From 263cc1bc2bccc85bdb49f053dd385bb2c13649f4 Mon Sep 17 00:00:00 2001 From: sergiorodriguezgarcia <113514397+sergiorodriguezgarcia@users.noreply.github.com> Date: Tue, 2 Apr 2024 17:44:42 +0200 Subject: [PATCH 001/118] feat: Adding new class to control the new api endpoints --- webapp/src/components/game/Game.js | 59 ++++++++++++++++++++++++++++++ webapp/src/pages/Game.jsx | 11 ++++++ 2 files changed, 70 insertions(+) create mode 100644 webapp/src/components/game/Game.js diff --git a/webapp/src/components/game/Game.js b/webapp/src/components/game/Game.js new file mode 100644 index 00000000..ddb9e1ef --- /dev/null +++ b/webapp/src/components/game/Game.js @@ -0,0 +1,59 @@ +import { HttpStatusCode } from "axios"; +import AuthManager from "components/auth/AuthManager"; + +const authManager = new AuthManager(); + +export async function newGame() { + try { + let requestAnswer = await authManager.getAxiosInstance().post(process.env.REACT_APP_API_ENDPOINT + "/games/new"); + if (HttpStatusCode.Ok === requestAnswer.status) { + return requestAnswer.data; + } + } catch { + + } +} + +export async function startRound(gameId) { + try { + let requestAnswer = await authManager.getAxiosInstance().post(process.env.REACT_APP_API_ENDPOINT + "/games/" + gameId + "/startRound"); + if (HttpStatusCode.Ok === requestAnswer.status) { + return requestAnswer.data; + } + } catch { + + } +} + +export async function getCurrentQuestion(gameId) { + try { + let requestAnswer = await authManager.getAxiosInstance().get(process.env.REACT_APP_API_ENDPOINT + "/games/" + gameId + "/question"); + if (HttpStatusCode.Ok === requestAnswer.status) { + return requestAnswer.data; + } + } catch { + + } +} + +export async function answerQuestion(gameId, aId) { + try { + let requestAnswer = await authManager.getAxiosInstance().post(process.env.REACT_APP_API_ENDPOINT + "/games/" + gameId + "/answer", {answer_id:aId}); + if (HttpStatusCode.Ok === requestAnswer.status) { + return requestAnswer.data; + } + } catch { + + } +} + +export async function getGameDetails(gameId) { + try { + let requestAnswer = await authManager.getAxiosInstance().get(process.env.REACT_APP_API_ENDPOINT + "/games/" + gameId + "/details"); + if (HttpStatusCode.Ok === requestAnswer.status) { + return requestAnswer.data; + } + } catch { + + } +} diff --git a/webapp/src/pages/Game.jsx b/webapp/src/pages/Game.jsx index 957c0e55..6f24f155 100644 --- a/webapp/src/pages/Game.jsx +++ b/webapp/src/pages/Game.jsx @@ -5,6 +5,7 @@ import { useNavigate } from "react-router-dom"; import Confetti from "react-confetti"; import ButtonEf from '../components/ButtonEf'; import {getQuestion, answerQuestion} from '../components/game/Questions'; +import {newGame} from '../components/game/Game'; import axios from "axios"; export default function Game() { @@ -12,6 +13,16 @@ export default function Game() { const [question, setQuestion] = useState(null); const [loading, setLoading] = useState(true); + const [gameId, setGameId] = useState(null); + + const generateGame = useCallback(async () => { + const result = await newGame(); + if (result !== undefined) { + setGameId(result.id); + } else { + navigate("/dashboard"); + } + }, [navigate]); const generateQuestion = useCallback(async () => { const result = await getQuestion(); From 9c857d2fe1a7e16db785c55bc4d2127254367ac1 Mon Sep 17 00:00:00 2001 From: sergiorodriguezgarcia <113514397+sergiorodriguezgarcia@users.noreply.github.com> Date: Tue, 2 Apr 2024 18:07:55 +0200 Subject: [PATCH 002/118] feat: Adding the changeLanguage --- webapp/src/components/game/Game.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/webapp/src/components/game/Game.js b/webapp/src/components/game/Game.js index ddb9e1ef..edcf768b 100644 --- a/webapp/src/components/game/Game.js +++ b/webapp/src/components/game/Game.js @@ -36,6 +36,17 @@ export async function getCurrentQuestion(gameId) { } } +export async function changeLanguage(gameId, language) { + try { + let requestAnswer = await authManager.getAxiosInstance().put(process.env.REACT_APP_API_ENDPOINT + "/games/" + gameId + "/language?language=" + language); + if (HttpStatusCode.Ok === requestAnswer.status) { + return requestAnswer.data; + } + } catch { + + } +} + export async function answerQuestion(gameId, aId) { try { let requestAnswer = await authManager.getAxiosInstance().post(process.env.REACT_APP_API_ENDPOINT + "/games/" + gameId + "/answer", {answer_id:aId}); From 247676ff0469a51b7d01278c80b4b60de92ca171 Mon Sep 17 00:00:00 2001 From: sergiorodriguezgarcia <113514397+sergiorodriguezgarcia@users.noreply.github.com> Date: Wed, 3 Apr 2024 18:06:32 +0200 Subject: [PATCH 003/118] feat: Adding the new game control to the react component --- webapp/src/pages/Game.jsx | 227 +++++++++++++++++++------------------- 1 file changed, 112 insertions(+), 115 deletions(-) diff --git a/webapp/src/pages/Game.jsx b/webapp/src/pages/Game.jsx index 6f24f155..5f5a4397 100644 --- a/webapp/src/pages/Game.jsx +++ b/webapp/src/pages/Game.jsx @@ -4,122 +4,119 @@ import { Center } from "@chakra-ui/layout"; import { useNavigate } from "react-router-dom"; import Confetti from "react-confetti"; import ButtonEf from '../components/ButtonEf'; -import {getQuestion, answerQuestion} from '../components/game/Questions'; -import {newGame} from '../components/game/Game'; +import { newGame, startRound, getCurrentQuestion, answerQuestion } from '../components/game/Game'; import axios from "axios"; export default function Game() { - const navigate = useNavigate(); - - const [question, setQuestion] = useState(null); - const [loading, setLoading] = useState(true); - const [gameId, setGameId] = useState(null); - - const generateGame = useCallback(async () => { - const result = await newGame(); - if (result !== undefined) { - setGameId(result.id); - } else { - navigate("/dashboard"); - } - }, [navigate]); - - const generateQuestion = useCallback(async () => { - const result = await getQuestion(); - if (result !== undefined) { - setQuestion(result); - } else { - navigate("/dashboard"); - } - }, [navigate]); - - useEffect(() => { - axios.defaults.headers.common["Authorization"] = "Bearer " + sessionStorage.getItem("jwtToken"); - const fetchQuestion = async () => { - setLoading(true); - await generateQuestion(); - setLoading(false); - }; - fetchQuestion(); - }, [generateQuestion, navigate]); - - const [answer, setAnswer] = useState({id:1, text:"answer1", category:"category1" }); - const [selectedOption, setSelectedOption] = useState(null); - const [nextDisabled, setNextDisabled] = useState(true); - const [roundNumber, setRoundNumber] = useState(1); - const [correctAnswers, setCorrectAnswers] = useState(0); - const [showConfetti, setShowConfetti] = useState(false); - - const answerButtonClick = (option) => { - setAnswer(question.answers[option-1]); - setSelectedOption((prevOption) => (prevOption === option ? null : option)); - const anyOptionSelected = option === selectedOption ? null : option; - setNextDisabled(anyOptionSelected === null); - }; - - const nextButtonClick = async () => { - const isCorrect = (await answerQuestion(question.id, answer.id)).wasCorrect; - - if (isCorrect) { - setCorrectAnswers((prevCorrectAnswers) => prevCorrectAnswers + 1); - setShowConfetti(true); - } - - setSelectedOption(null); - - const nextRoundNumber = roundNumber + 1; - if (nextRoundNumber > 9) - navigate("/dashboard/game/results", { state: { correctAnswers: correctAnswers + (isCorrect ? 1 : 0) } }); - else { - setRoundNumber(nextRoundNumber); - setNextDisabled(true); - await generateQuestion(); - } - }; - - useEffect(() => { // stop the confeti after 3000 milliseconds - let timeout; - if (showConfetti) - timeout = setTimeout(() => { setShowConfetti(false); }, 3000); - return () => clearTimeout(timeout); - }, [showConfetti]); - - return ( -
- {`Round ${roundNumber}`} - - {`Correct answers: ${correctAnswers}`} - - - {loading ? ( - - ) : ( - <> - {question.content} - - - answerButtonClick(1)} /> - answerButtonClick(2)} /> - - - - - - - {showConfetti && ( - - )} - - )} - -
- ); + const navigate = useNavigate(); + + const [question, setQuestion] = useState(null); + const [loading, setLoading] = useState(true); + const [gameId, setGameId] = useState(null); // State to store the game ID + + const generateQuestion = useCallback(async () => { + const result = await getCurrentQuestion(gameId); // Fetch current question based on game ID + if (result !== undefined) { + setQuestion(result); + } else { + navigate("/dashboard"); + } + }, [gameId, navigate]); + + useEffect(() => { + axios.defaults.headers.common["Authorization"] = "Bearer " + sessionStorage.getItem("jwtToken"); + const initializeGame = async () => { + setLoading(true); + const newGameResponse = await newGame(); // Create a new game + if (newGameResponse) { + setGameId(newGameResponse.id); // Store the game ID + await startRound(newGameResponse.id); // Start the first round of the game + await generateQuestion(); // Fetch the first question + } else { + navigate("/dashboard"); + } + setLoading(false); + }; + initializeGame(); + }, [generateQuestion, navigate]); + + const [answer, setAnswer] = useState({id:1, text:"answer1", category:"category1" }); + const [selectedOption, setSelectedOption] = useState(null); + const [nextDisabled, setNextDisabled] = useState(true); + const [roundNumber, setRoundNumber] = useState(1); + const [correctAnswers, setCorrectAnswers] = useState(0); + const [showConfetti, setShowConfetti] = useState(false); + + const answerButtonClick = (option) => { + setAnswer(question.answers[option-1]); + setSelectedOption((prevOption) => (prevOption === option ? null : option)); + const anyOptionSelected = option === selectedOption ? null : option; + setNextDisabled(anyOptionSelected === null); + }; + + const nextButtonClick = async () => { + const isCorrect = (await answerQuestion(gameId, answer.id)).wasCorrect; + + if (isCorrect) { + setCorrectAnswers((prevCorrectAnswers) => prevCorrectAnswers + 1); + setShowConfetti(true); + } + + setSelectedOption(null); + + const nextRoundNumber = roundNumber + 1; + if (nextRoundNumber > 9) + navigate("/dashboard/game/results", { state: { correctAnswers: correctAnswers + (isCorrect ? 1 : 0) } }); + else { + setRoundNumber(nextRoundNumber); + setNextDisabled(true); + await generateQuestion(); + } + }; + + useEffect(() => { // stop the confeti after 3000 milliseconds + let timeout; + if (showConfetti) + timeout = setTimeout(() => { setShowConfetti(false); }, 3000); + return () => clearTimeout(timeout); + }, [showConfetti]); + + return ( +
+ {`Round ${roundNumber}`} + + {`Correct answers: ${correctAnswers}`} + + + {loading ? ( + + ) : ( + <> + {question.content} + + + answerButtonClick(1)} /> + answerButtonClick(2)} /> + + + + + + + {showConfetti && ( + + )} + + )} + +
+ ); } \ No newline at end of file From a2023aab37d36386ccd978b81a4452f36f1e7dfb Mon Sep 17 00:00:00 2001 From: sergiorodriguezgarcia <113514397+sergiorodriguezgarcia@users.noreply.github.com> Date: Fri, 5 Apr 2024 16:44:29 +0200 Subject: [PATCH 004/118] fix: Making the About page look correctly --- webapp/src/pages/About.jsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/webapp/src/pages/About.jsx b/webapp/src/pages/About.jsx index af99bb39..ff0de694 100644 --- a/webapp/src/pages/About.jsx +++ b/webapp/src/pages/About.jsx @@ -17,7 +17,8 @@ export default function About() { }; return ( -
+
setIsMenuOpen(true)} /> setIsMenuOpen(false)} changeLanguage={changeLanguage} currentLanguage={currentLanguage} isDashboard={false}/> From 9c3282ccee4412475eb85d7d7b9990a113b37810 Mon Sep 17 00:00:00 2001 From: sergiorodriguezgarcia <113514397+sergiorodriguezgarcia@users.noreply.github.com> Date: Sat, 6 Apr 2024 14:09:40 +0200 Subject: [PATCH 005/118] feat: Adding new functionality to the game --- webapp/src/pages/About.jsx | 77 -------------- webapp/src/pages/Game.jsx | 205 ++++++++++++++++++++++--------------- 2 files changed, 125 insertions(+), 157 deletions(-) diff --git a/webapp/src/pages/About.jsx b/webapp/src/pages/About.jsx index c0e93835..58a79186 100644 --- a/webapp/src/pages/About.jsx +++ b/webapp/src/pages/About.jsx @@ -1,79 +1,3 @@ -<<<<<<< HEAD -import React, { useState } from "react"; -import { useTranslation } from 'react-i18next'; -import { Center, Heading, Stack, Box, Text, Table, Thead, Tr, Td, Th, Tbody, Container } from '@chakra-ui/react'; -import { InfoIcon } from '@chakra-ui/icons'; - -import LateralMenu from '../components/LateralMenu'; -import MenuButton from '../components/MenuButton'; -import GoBack from "components/GoBack"; - -export default function About() { - const { t, i18n } = useTranslation(); - const currentLanguage = i18n.language; - const [isMenuOpen, setIsMenuOpen] = useState(false); - - const changeLanguage = (selectedLanguage) => { - i18n.changeLanguage(selectedLanguage); - }; - - return ( -
- setIsMenuOpen(true)} /> - setIsMenuOpen(false)} changeLanguage={changeLanguage} currentLanguage={currentLanguage} isDashboard={false}/> - - - - {t('about.title')} - - - {t("about.description1")} -

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{t("about.table1")}{t("about.table2")}
Gonzalo Alonso FernándezUO282104
Sergio Rodríguez GarcíaUO282598
Jorge Joaquín Gancedo FernándezUO282161
Darío Gutiérrez MoriUO282435
Sergio Quintana FernándezUO288090
Diego Villanueva BerrosUO283615
Gonzalo Suárez LosadaUO283928
- -
-
-
- ); -======= import React, { useState } from "react"; import { useTranslation } from 'react-i18next'; import { Center, Heading, Stack, Box, Text, Table, Thead, Tr, Td, Th, Tbody, Container } from '@chakra-ui/react'; @@ -147,5 +71,4 @@ export default function About() {
); ->>>>>>> develop } \ No newline at end of file diff --git a/webapp/src/pages/Game.jsx b/webapp/src/pages/Game.jsx index 8ba4e232..14869aed 100644 --- a/webapp/src/pages/Game.jsx +++ b/webapp/src/pages/Game.jsx @@ -1,5 +1,5 @@ import React, { useState, useEffect, useCallback } from "react"; -import { Grid, Flex, Heading, Button, Box, Text, Spinner } from "@chakra-ui/react"; +import { Grid, Flex, Heading, Button, Box, Text, Spinner, CircularProgress } from "@chakra-ui/react"; import { Center } from "@chakra-ui/layout"; import { useNavigate } from "react-router-dom"; import { useTranslation } from "react-i18next"; @@ -13,42 +13,66 @@ import MenuButton from '../components/MenuButton'; export default function Game() { const navigate = useNavigate(); - const [question, setQuestion] = useState(null); const [loading, setLoading] = useState(true); - const [gameId, setGameId] = useState(null); // State to store the game ID + const [gameId, setGameId] = useState(null); + const [question, setQuestion] = useState(null); + const [answer, setAnswer] = useState({id:1, text:"answer1", category:"category1" }); + const [selectedOption, setSelectedOption] = useState(null); + const [nextDisabled, setNextDisabled] = useState(true); + const [roundNumber, setRoundNumber] = useState(1); + const [correctAnswers, setCorrectAnswers] = useState(0); + const [showConfetti, setShowConfetti] = useState(false); + const [timeElapsed, setTimeElapsed] = useState(0); - const generateQuestion = useCallback(async () => { - const result = await getCurrentQuestion(gameId); // Fetch current question based on game ID - if (result !== undefined) { - setQuestion(result); - } else { - navigate("/dashboard"); - } - }, [gameId, navigate]); + const { t, i18n } = useTranslation(); + const [isMenuOpen, setIsMenuOpen] = useState(false); + + const changeLanguage = (selectedLanguage) => { + i18n.changeLanguage(selectedLanguage); + }; useEffect(() => { axios.defaults.headers.common["Authorization"] = "Bearer " + sessionStorage.getItem("jwtToken"); const initializeGame = async () => { - setLoading(true); - const newGameResponse = await newGame(); // Create a new game - if (newGameResponse) { - setGameId(newGameResponse.id); // Store the game ID - await startRound(newGameResponse.id); // Start the first round of the game - await generateQuestion(); // Fetch the first question - } else { + try { + const newGameResponse = await newGame(); + if (newGameResponse) { + setGameId(newGameResponse.id); + setLoading(false); + await startRound(newGameResponse.id); + startTimer(); + } else { + navigate("/dashboard"); + } + } catch (error) { + console.error("Error initializing game:", error); navigate("/dashboard"); } - setLoading(false); }; + initializeGame(); - }, [generateQuestion, navigate]); + }, [navigate]); - const [answer, setAnswer] = useState({id:1, text:"answer1", category:"category1" }); - const [selectedOption, setSelectedOption] = useState(null); - const [nextDisabled, setNextDisabled] = useState(true); - const [roundNumber, setRoundNumber] = useState(1); - const [correctAnswers, setCorrectAnswers] = useState(0); - const [showConfetti, setShowConfetti] = useState(false); + const generateQuestion = useCallback(async () => { + try { + const result = await getCurrentQuestion(gameId); + if (result !== undefined) { + setQuestion(result); + setTimeElapsed(0); + } else { + navigate("/dashboard"); + } + } catch (error) { + console.error("Error fetching question:", error); + navigate("/dashboard"); + } + }, [gameId, navigate]); + + useEffect(() => { + if (gameId !== null) { + generateQuestion(); + } + }, [gameId, generateQuestion]); const answerButtonClick = (option) => { setAnswer(question.answers[option-1]); @@ -58,78 +82,99 @@ export default function Game() { }; const nextButtonClick = async () => { - const isCorrect = (await answerQuestion(gameId, answer.id)).wasCorrect; + try { + const isCorrect = (await answerQuestion(gameId, answer.id)).wasCorrect; - if (isCorrect) { - setCorrectAnswers((prevCorrectAnswers) => prevCorrectAnswers + 1); - setShowConfetti(true); - } + if (isCorrect) { + setCorrectAnswers((prevCorrectAnswers) => prevCorrectAnswers + 1); + setShowConfetti(true); + } - setSelectedOption(null); + setSelectedOption(null); - const nextRoundNumber = roundNumber + 1; - if (nextRoundNumber > 9) - navigate("/dashboard/game/results", { state: { correctAnswers: correctAnswers + (isCorrect ? 1 : 0) } }); - else { - setRoundNumber(nextRoundNumber); - setNextDisabled(true); - await generateQuestion(); + const nextRoundNumber = roundNumber + 1; + if (nextRoundNumber > 9) + navigate("/dashboard/game/results", { state: { correctAnswers: correctAnswers + (isCorrect ? 1 : 0) } }); + else { + setRoundNumber(nextRoundNumber); + setNextDisabled(true); + await generateQuestion(); + } + } catch (error) { + console.error("Error processing next question:", error); + navigate("/dashboard"); } }; - useEffect(() => { // stop the confeti after 3000 milliseconds + useEffect(() => { let timeout; if (showConfetti) timeout = setTimeout(() => { setShowConfetti(false); }, 3000); return () => clearTimeout(timeout); }, [showConfetti]); - const { t, i18n } = useTranslation(); - const [isMenuOpen, setIsMenuOpen] = useState(false); + useEffect(() => { + if (timeElapsed >= 30) { + nextButtonClick(); + } else { + const timer = setTimeout(() => { + setTimeElapsed((prevTime) => prevTime + 1); + }, 1000); + return () => clearTimeout(timer); + } + }, [timeElapsed, nextButtonClick]); - const changeLanguage = (selectedLanguage) => { - i18n.changeLanguage(selectedLanguage); + const startTimer = () => { + const timer = setTimeout(() => { + setTimeElapsed((prevTime) => prevTime + 1); + }, 1000); + return () => clearTimeout(timer); }; return (
- setIsMenuOpen(true)} /> + setIsMenuOpen(true)} /> setIsMenuOpen(false)} changeLanguage={changeLanguage} isDashboard={false}/> - {t("game.round") + `${roundNumber}`} - - {`Correct answers: ${correctAnswers}`} - - - {loading ? ( - - ) : ( - <> - {question.content} - - - answerButtonClick(1)} /> - answerButtonClick(2)} /> - - - - - - - {showConfetti && ( - - )} - - )} - -
+ {t("game.round") + `${roundNumber}`} + + {`Correct answers: ${correctAnswers}`} + + + + + {loading ? ( + + ) : ( + question && ( + <> + {question.content} + + + {question.answers.map((answer, index) => ( + answerButtonClick(index + 1)} /> + ))} + + + + + + + {showConfetti && ( + + )} + + ) + )} + +
); } \ No newline at end of file From 8c35c31422a5803a1fd6b9be3554bc34285393cf Mon Sep 17 00:00:00 2001 From: sergiorodriguezgarcia <113514397+sergiorodriguezgarcia@users.noreply.github.com> Date: Sat, 6 Apr 2024 14:30:56 +0200 Subject: [PATCH 006/118] fix: Changin the setGame --- webapp/src/pages/Game.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webapp/src/pages/Game.jsx b/webapp/src/pages/Game.jsx index 14869aed..15c2d77f 100644 --- a/webapp/src/pages/Game.jsx +++ b/webapp/src/pages/Game.jsx @@ -37,9 +37,9 @@ export default function Game() { try { const newGameResponse = await newGame(); if (newGameResponse) { - setGameId(newGameResponse.id); setLoading(false); await startRound(newGameResponse.id); + setGameId(newGameResponse.id); startTimer(); } else { navigate("/dashboard"); From 290dcaa4869123681fc637ef701c3a8abf7bb1a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Su=C3=A1rez=20Losada?= Date: Sat, 6 Apr 2024 15:00:32 +0200 Subject: [PATCH 007/118] feat: added user stories for login acceptance tests --- .../user_stories/login_user_stories.txt | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 webapp/e2e/acceptance/user_stories/login_user_stories.txt diff --git a/webapp/e2e/acceptance/user_stories/login_user_stories.txt b/webapp/e2e/acceptance/user_stories/login_user_stories.txt new file mode 100644 index 00000000..cf23663e --- /dev/null +++ b/webapp/e2e/acceptance/user_stories/login_user_stories.txt @@ -0,0 +1,35 @@ +User story: + As a registered user + I want to log in + so that I can play the game + + +Case 1: + SCENARIO: a registered user wants to log in using his correct credentials + GIVEN: a registered user + WHEN: user enters the log in screen + user writes his credentials properly + + THEN: user is able to log in successfully + +Case 2: + SCENARIO: a registered user wants to log in using his incorrect credentials + GIVEN: a registered user + WHEN: user enters the log in screen + user writes his credentials wrong + + THEN: user is not able to log in + + +User story: + As an unregistered user + I want to log in + so that I can play the game + +Case 1: + SCENARIO: a registered user wants to log in without using an account + GIVEN: a registered user + WHEN: user enters the log in screen + user leaves the credentials in blank + + THEN: user is not able to log in From b6dc1c042c30daccd22e41377a035aad7ec2e5db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Su=C3=A1rez=20Losada?= Date: Sat, 6 Apr 2024 16:32:04 +0200 Subject: [PATCH 008/118] feat: added user stories for playing a game acceptance tests --- .../user_stories/playing_game_user.txt | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 webapp/e2e/acceptance/user_stories/playing_game_user.txt diff --git a/webapp/e2e/acceptance/user_stories/playing_game_user.txt b/webapp/e2e/acceptance/user_stories/playing_game_user.txt new file mode 100644 index 00000000..b8636c93 --- /dev/null +++ b/webapp/e2e/acceptance/user_stories/playing_game_user.txt @@ -0,0 +1,20 @@ +User story: + As a logged user + I want to start a game + so that I can play the game + +SCENARIO: a logged user wants to play a new game +GIVEN: a logged user +WHEN: clicking the button to start a new game +THEN: a new game is created and the first question appears on the screen + + +User story: + As a non-registered user + I want to start a game + so that I can play the game + +SCENARIO: a non-registered user tries to access to a new game via URL +GIVEN: a non-registered user +WHEN: trying to create a new game via URL +THEN: game is not created as there's no open session and user is redirected to log in screen From df0719e91a2533ebafcdf8643e865cf032c9f5cc Mon Sep 17 00:00:00 2001 From: sergioqfeg1 Date: Sat, 6 Apr 2024 17:26:02 +0200 Subject: [PATCH 009/118] fix: create 2 validate --- api/src/main/resources/application.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/main/resources/application.properties b/api/src/main/resources/application.properties index 8f7af9da..14b5bf17 100644 --- a/api/src/main/resources/application.properties +++ b/api/src/main/resources/application.properties @@ -1,6 +1,6 @@ JWT_EXPIRATION_MS=86400000 REFRESH_TOKEN_DURATION_MS=86400000 -spring.jpa.hibernate.ddl-auto=create +spring.jpa.hibernate.ddl-auto=validate spring.datasource.url=${DATABASE_URL} spring.datasource.username=${DATABASE_USER} spring.datasource.password=${DATABASE_PASSWORD} From 601f71b9143a188fb4815a5f29a44c25cef2a280 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Su=C3=A1rez=20Losada?= Date: Sat, 6 Apr 2024 17:45:56 +0200 Subject: [PATCH 010/118] feat: added user stories for seeing statistics acceptance tests --- .../user_stories/seeing_stats_stories.txt | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 webapp/e2e/acceptance/user_stories/seeing_stats_stories.txt diff --git a/webapp/e2e/acceptance/user_stories/seeing_stats_stories.txt b/webapp/e2e/acceptance/user_stories/seeing_stats_stories.txt new file mode 100644 index 00000000..e880b67a --- /dev/null +++ b/webapp/e2e/acceptance/user_stories/seeing_stats_stories.txt @@ -0,0 +1,22 @@ +SCENARIO: a fresh new logged user wants to see its stats where +GIVEN: a logged user which has no games yet +WHEN: clicking the button for seeing the stats +THEN: + + +SCENARIO: a logged user with many games wants to see its stats +GIVEN: a logged user with many games and a full ladder board (many other users with many other games) +WHEN: clicking the button for seeing stats +THEN: it successfully displays both, the ladder board and the logged user statistics + + +User story: + As a non-logged user + I want to see the statistics + so that I know the current global ranking + +SCENARIO: a non-logged user wants to access the global ranking +GIVEN: a logged user +WHEN: clicking the button for seeing stats +THEN: he cannot see it as the web redirects it to the log in screen. + From e89cb695c5688f219540d84d37828f99f5ab57ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Su=C3=A1rez=20Losada?= Date: Sat, 6 Apr 2024 17:46:52 +0200 Subject: [PATCH 011/118] feat: added user stories for registering acceptance tests --- .../user_stories/register_user_stories.txt | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 webapp/e2e/acceptance/user_stories/register_user_stories.txt diff --git a/webapp/e2e/acceptance/user_stories/register_user_stories.txt b/webapp/e2e/acceptance/user_stories/register_user_stories.txt new file mode 100644 index 00000000..0247fe51 --- /dev/null +++ b/webapp/e2e/acceptance/user_stories/register_user_stories.txt @@ -0,0 +1,34 @@ +User story: + As an unregistered user + I want to register + so that I can play the game + +Case 1: + SCENARIO: a new user registers into the game successfully + GIVEN: an unregistered user + WHEN: user enters the login screen + user types his information properly + THEN: a new user is created + + +Case 2: + SCENARIO: a new user tries to register leaving a blank field + GIVEN: an unregistered user + WHEN: user enters the login screen + user types leaves the username field blank, fills all the other fields properly + THEN: the user is unable to create a new account + +Case 3: + SCENARIO: a new user tries to register having a wrong email format + GIVEN: an unregistered user + WHEN: user enters the login screen + user types sets an incorrect formatted email, fills all the other fields properly + THEN: the user is unable to create a new account + + +Case 4: + SCENARIO: a new user tries to register placing a username already in use + GIVEN: an unregistered user + WHEN: user enters the login screen + user types sets a username which is already in use, fills all the other fields properly + THEN: the user is unable to create a new account From 88dcd12cac5ae8ef623039bd7704432f935ec345 Mon Sep 17 00:00:00 2001 From: sergioqfeg1 Date: Sat, 6 Apr 2024 18:35:41 +0200 Subject: [PATCH 012/118] fix: statistics tests --- .../quizapi/statistics/StatisticsService.java | 3 +- .../statistics/StatisticsServiceTest.java | 33 ++++++++++++++++--- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/api/src/main/java/lab/en2b/quizapi/statistics/StatisticsService.java b/api/src/main/java/lab/en2b/quizapi/statistics/StatisticsService.java index 0f0ec3f6..49110478 100644 --- a/api/src/main/java/lab/en2b/quizapi/statistics/StatisticsService.java +++ b/api/src/main/java/lab/en2b/quizapi/statistics/StatisticsService.java @@ -7,6 +7,7 @@ import org.springframework.security.core.Authentication; import org.springframework.stereotype.Service; +import java.util.ArrayList; import java.util.Comparator; import java.util.List; import java.util.stream.Collectors; @@ -25,7 +26,7 @@ public StatisticsResponseDto getStatisticsForUser(Authentication authentication) } public List getTopTenStatistics(){ - List all = statisticsRepository.findAll(); + List all = new ArrayList<>(statisticsRepository.findAll()); all.sort(Comparator.comparing(Statistics::getCorrectRate).reversed()); List topTen = all.stream().limit(10).collect(Collectors.toList()); return topTen.stream().map(statisticsResponseDtoMapper).collect(Collectors.toList()); diff --git a/api/src/test/java/lab/en2b/quizapi/statistics/StatisticsServiceTest.java b/api/src/test/java/lab/en2b/quizapi/statistics/StatisticsServiceTest.java index 9a91a629..4d60c11b 100644 --- a/api/src/test/java/lab/en2b/quizapi/statistics/StatisticsServiceTest.java +++ b/api/src/test/java/lab/en2b/quizapi/statistics/StatisticsServiceTest.java @@ -118,14 +118,37 @@ public void getStatisticsForUserTest(){ Assertions.assertEquals(defaultStatisticsResponseDto1, result); } - /*@Test + @Test public void getTopTenStatisticsTestWhenThereAreNotTen(){ when(statisticsRepository.findAll()).thenReturn(List.of(defaultStatistics2, defaultStatistics1)); - when(statisticsResponseDtoMapper.apply(any())).thenReturn(defaultStatisticsResponseDto1); - when(statisticsResponseDtoMapper.apply(any())).thenReturn(defaultStatisticsResponseDto2); - when(statisticsResponseDtoMapper.apply(any())).thenReturn(defaultStatisticsResponseDto1); + when(statisticsResponseDtoMapper.apply(defaultStatistics1)).thenReturn(defaultStatisticsResponseDto1); + when(statisticsResponseDtoMapper.apply(defaultStatistics2)).thenReturn(defaultStatisticsResponseDto2); List result = statisticsService.getTopTenStatistics(); Assertions.assertEquals(List.of(defaultStatisticsResponseDto2,defaultStatisticsResponseDto1), result); - }*/ + } + + @Test + public void getTopTenStatisticsTestWhenThereAreNotTenAndAreEqual(){ + Statistics defaultStatistics3 = Statistics.builder() + .id(2L) + .user(defaultUser) + .correct(5L) + .wrong(5L) + .total(10L) + .build(); + StatisticsResponseDto defaultStatisticsResponseDto3 = StatisticsResponseDto.builder() + .id(2L) + .right(5L) + .wrong(5L) + .total(10L) + .correctRate(50L) + .user(defaultUserResponseDto) + .build(); + when(statisticsRepository.findAll()).thenReturn(List.of(defaultStatistics1, defaultStatistics3)); + when(statisticsResponseDtoMapper.apply(defaultStatistics1)).thenReturn(defaultStatisticsResponseDto1); + when(statisticsResponseDtoMapper.apply(defaultStatistics3)).thenReturn(defaultStatisticsResponseDto3); + List result = statisticsService.getTopTenStatistics(); + Assertions.assertEquals(List.of(defaultStatisticsResponseDto1,defaultStatisticsResponseDto3), result); + } } From f3dd08f90a55468bab81cf1e1af53ac2173ab12e Mon Sep 17 00:00:00 2001 From: sergioqfeg1 Date: Sat, 6 Apr 2024 18:56:21 +0200 Subject: [PATCH 013/118] feat: findRandomQuestion for GameService --- .../en2b/quizapi/questions/question/QuestionService.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/api/src/main/java/lab/en2b/quizapi/questions/question/QuestionService.java b/api/src/main/java/lab/en2b/quizapi/questions/question/QuestionService.java index 5710f7dc..498e5c8e 100644 --- a/api/src/main/java/lab/en2b/quizapi/questions/question/QuestionService.java +++ b/api/src/main/java/lab/en2b/quizapi/questions/question/QuestionService.java @@ -43,6 +43,15 @@ public QuestionResponseDto getRandomQuestion(String lang) { return questionResponseDtoMapper.apply(q); } + + public Question findRandomQuestion(String lang){ + if (lang==null || lang.isBlank()) { + lang = "en"; + } + Question q = questionRepository.findRandomQuestion(lang); + loadAnswers(q); + return q; + } public QuestionResponseDto getQuestionById(Long id) { return questionResponseDtoMapper.apply(questionRepository.findById(id).orElseThrow()); From 9497ef618d41fa3c0292fbe2e6b86c6b22e4e452 Mon Sep 17 00:00:00 2001 From: sergioqfeg1 Date: Sat, 6 Apr 2024 18:57:05 +0200 Subject: [PATCH 014/118] fix: nextRound adds answers to the question --- api/src/main/java/lab/en2b/quizapi/game/GameService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/main/java/lab/en2b/quizapi/game/GameService.java b/api/src/main/java/lab/en2b/quizapi/game/GameService.java index 447b3995..c0307d67 100644 --- a/api/src/main/java/lab/en2b/quizapi/game/GameService.java +++ b/api/src/main/java/lab/en2b/quizapi/game/GameService.java @@ -45,7 +45,7 @@ public GameResponseDto newGame(Authentication authentication) { public GameResponseDto startRound(Long id, Authentication authentication) { Game game = gameRepository.findByIdForUser(id, userService.getUserByAuthentication(authentication).getId()).orElseThrow(); - game.newRound(questionRepository.findRandomQuestion(game.getLanguage())); + game.newRound(questionService.findRandomQuestion(game.getLanguage())); if (game.isGameOver()){ Statistics statistics = Statistics.builder() .user(game.getUser()) From 60bb06521d520bf70781b6e3671f9a6e985f100f Mon Sep 17 00:00:00 2001 From: sergioqfeg1 Date: Sat, 6 Apr 2024 18:57:55 +0200 Subject: [PATCH 015/118] fix: errors in game tests --- .../en2b/quizapi/game/GameServiceTest.java | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/api/src/test/java/lab/en2b/quizapi/game/GameServiceTest.java b/api/src/test/java/lab/en2b/quizapi/game/GameServiceTest.java index d42ef727..97deeffb 100644 --- a/api/src/test/java/lab/en2b/quizapi/game/GameServiceTest.java +++ b/api/src/test/java/lab/en2b/quizapi/game/GameServiceTest.java @@ -164,7 +164,7 @@ public void newGame(){ public void startRound(){ when(gameRepository.findByIdForUser(any(), any())).thenReturn(Optional.of(defaultGame)); when(gameRepository.save(any())).thenAnswer(invocation -> invocation.getArgument(0)); - when(questionRepository.findRandomQuestion(any())).thenReturn(defaultQuestion); + when(questionService.findRandomQuestion(any())).thenReturn(defaultQuestion); when(userService.getUserByAuthentication(authentication)).thenReturn(defaultUser); GameResponseDto gameDto = gameService.startRound(1L, authentication); GameResponseDto result = defaultGameResponseDto; @@ -177,7 +177,7 @@ public void startRound(){ @Test public void startRoundGameOver(){ when(gameRepository.findByIdForUser(any(), any())).thenReturn(Optional.of(defaultGame)); - when(questionRepository.findRandomQuestion(any())).thenReturn(defaultQuestion); + when(questionService.findRandomQuestion(any())).thenReturn(defaultQuestion); when(userService.getUserByAuthentication(authentication)).thenReturn(defaultUser); defaultGame.setActualRound(10); assertThrows(IllegalStateException.class, () -> gameService.startRound(1L,authentication)); @@ -187,7 +187,7 @@ public void startRoundGameOver(){ public void startRoundWhenRoundNotFinished(){ when(gameRepository.findByIdForUser(any(), any())).thenReturn(Optional.of(defaultGame)); when(gameRepository.save(any())).thenAnswer(invocation -> invocation.getArgument(0)); - when(questionRepository.findRandomQuestion(any())).thenReturn(defaultQuestion); + when(questionService.findRandomQuestion(any())).thenReturn(defaultQuestion); when(userService.getUserByAuthentication(authentication)).thenReturn(defaultUser); gameService.startRound(1L,authentication); assertThrows(IllegalStateException.class, () -> gameService.startRound(1L,authentication)); @@ -197,7 +197,7 @@ public void startRoundWhenRoundNotFinished(){ public void getCurrentQuestion() { when(gameRepository.findByIdForUser(any(), any())).thenReturn(Optional.of(defaultGame)); when(gameRepository.save(any())).thenAnswer(invocation -> invocation.getArgument(0)); - when(questionRepository.findRandomQuestion(any())).thenReturn(defaultQuestion); + when(questionService.findRandomQuestion(any())).thenReturn(defaultQuestion); when(userService.getUserByAuthentication(authentication)).thenReturn(defaultUser); gameService.startRound(1L,authentication); QuestionResponseDto questionDto = gameService.getCurrentQuestion(1L,authentication); @@ -215,7 +215,7 @@ public void getCurrentQuestionRoundNotStarted() { public void getCurrentQuestionRoundFinished() { when(gameRepository.findByIdForUser(any(), any())).thenReturn(Optional.of(defaultGame)); when(gameRepository.save(any())).thenAnswer(invocation -> invocation.getArgument(0)); - when(questionRepository.findRandomQuestion(any())).thenReturn(defaultQuestion); + when(questionService.findRandomQuestion(any())).thenReturn(defaultQuestion); when(userService.getUserByAuthentication(authentication)).thenReturn(defaultUser); gameService.startRound(1L,authentication); defaultGame.setRoundStartTime(LocalDateTime.now().minusSeconds(100)); @@ -227,7 +227,7 @@ public void getCurrentQuestionGameFinished() { when(gameRepository.findByIdForUser(any(), any())).thenReturn(Optional.of(defaultGame)); when(userService.getUserByAuthentication(authentication)).thenReturn(defaultUser); when(gameRepository.save(any())).thenAnswer(invocation -> invocation.getArgument(0)); - when(questionRepository.findRandomQuestion(any())).thenReturn(defaultQuestion); + when(questionService.findRandomQuestion(any())).thenReturn(defaultQuestion); gameService.startRound(1L,authentication); defaultGame.setActualRound(10); assertThrows(IllegalStateException.class, () -> gameService.getCurrentQuestion(1L,authentication)); @@ -238,7 +238,7 @@ public void answerQuestionCorrectly(){ when(gameRepository.findByIdForUser(any(), any())).thenReturn(Optional.of(defaultGame)); when(gameRepository.save(any())).thenAnswer(invocation -> invocation.getArgument(0)); when(userService.getUserByAuthentication(authentication)).thenReturn(defaultUser); - when(questionRepository.findRandomQuestion(any())).thenReturn(defaultQuestion); + when(questionService.findRandomQuestion(any())).thenReturn(defaultQuestion); gameService.newGame(authentication); gameService.startRound(1L, authentication); gameService.answerQuestion(1L, new GameAnswerDto(1L), authentication); @@ -252,7 +252,7 @@ public void answerQuestionIncorrectly(){ when(gameRepository.findByIdForUser(any(), any())).thenReturn(Optional.of(defaultGame)); when(gameRepository.save(any())).thenAnswer(invocation -> invocation.getArgument(0)); when(userService.getUserByAuthentication(authentication)).thenReturn(defaultUser); - when(questionRepository.findRandomQuestion(any())).thenReturn(defaultQuestion); + when(questionService.findRandomQuestion(any())).thenReturn(defaultQuestion); gameService.newGame(authentication); gameService.startRound(1L, authentication); gameService.answerQuestion(1L, new GameAnswerDto(2L), authentication); @@ -266,7 +266,7 @@ public void answerQuestionWhenGameHasFinished(){ when(gameRepository.findByIdForUser(any(), any())).thenReturn(Optional.of(defaultGame)); when(gameRepository.save(any())).thenAnswer(invocation -> invocation.getArgument(0)); when(userService.getUserByAuthentication(authentication)).thenReturn(defaultUser); - when(questionRepository.findRandomQuestion(any())).thenReturn(defaultQuestion); + when(questionService.findRandomQuestion(any())).thenReturn(defaultQuestion); gameService.newGame(authentication); gameService.startRound(1L, authentication); defaultGame.setActualRound(30); @@ -278,7 +278,7 @@ public void answerQuestionWhenRoundHasFinished(){ when(gameRepository.findByIdForUser(any(), any())).thenReturn(Optional.of(defaultGame)); when(gameRepository.save(any())).thenAnswer(invocation -> invocation.getArgument(0)); when(userService.getUserByAuthentication(authentication)).thenReturn(defaultUser); - when(questionRepository.findRandomQuestion(any())).thenReturn(defaultQuestion); + when(questionService.findRandomQuestion(any())).thenReturn(defaultQuestion); gameService.newGame(authentication); gameService.startRound(1L, authentication); defaultGame.setRoundStartTime(LocalDateTime.now().minusSeconds(100)); @@ -290,7 +290,7 @@ public void answerQuestionInvalidId(){ when(gameRepository.findByIdForUser(any(), any())).thenReturn(Optional.of(defaultGame)); when(gameRepository.save(any())).thenAnswer(invocation -> invocation.getArgument(0)); when(userService.getUserByAuthentication(authentication)).thenReturn(defaultUser); - when(questionRepository.findRandomQuestion(any())).thenReturn(defaultQuestion); + when(questionService.findRandomQuestion(any())).thenReturn(defaultQuestion); gameService.newGame(authentication); gameService.startRound(1L, authentication); assertThrows(IllegalArgumentException.class, () -> gameService.answerQuestion(1L, new GameAnswerDto(3L), authentication)); From a689f329bed70880a81881d7d0892aca19f57e97 Mon Sep 17 00:00:00 2001 From: jjgancfer Date: Sat, 6 Apr 2024 19:02:42 +0200 Subject: [PATCH 016/118] fix: map deploy port to 80:3000 --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 902d8f0c..3e8e23bf 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -65,7 +65,7 @@ services: environment: - REACT_APP_API_ENDPOINT=${API_URI} ports: - - "3000:3000" + - "80:3000" prometheus: image: prom/prometheus From e527ca21ba832355ec9953c7e739392ba9360303 Mon Sep 17 00:00:00 2001 From: jjgancfer Date: Sat, 6 Apr 2024 19:04:42 +0200 Subject: [PATCH 017/118] fix: map correct url to deploy --- webapp/src/components/LateralMenu.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webapp/src/components/LateralMenu.jsx b/webapp/src/components/LateralMenu.jsx index e3a0a075..c9066075 100644 --- a/webapp/src/components/LateralMenu.jsx +++ b/webapp/src/components/LateralMenu.jsx @@ -25,7 +25,7 @@ const LateralMenu = ({ isOpen, onClose, changeLanguage, isDashboard }) => { }; const handleApiClick = () => { - window.open("http://localhost:8080/swagger/swagger-ui/index.html#/auth-controller/registerUser", "_blank", "noopener"); + window.open(`http://${process.env.REACT_APP_API_ENDPOINT}/swagger/swagger-ui/index.html#/auth-controller/registerUser`, "_blank", "noopener"); }; const handleLogout = async () => { From d6209310bfc58f3ac3809367340b00f507ff6410 Mon Sep 17 00:00:00 2001 From: Gonzalo Alonso Fernandez Date: Sat, 6 Apr 2024 19:05:51 +0200 Subject: [PATCH 018/118] feat: changing the game. --- webapp/src/pages/Game.jsx | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/webapp/src/pages/Game.jsx b/webapp/src/pages/Game.jsx index 15c2d77f..b0282b7f 100644 --- a/webapp/src/pages/Game.jsx +++ b/webapp/src/pages/Game.jsx @@ -4,7 +4,6 @@ import { Center } from "@chakra-ui/layout"; import { useNavigate } from "react-router-dom"; import { useTranslation } from "react-i18next"; import Confetti from "react-confetti"; -import ButtonEf from '../components/ButtonEf'; import { newGame, startRound, getCurrentQuestion, answerQuestion } from '../components/game/Game'; import axios from "axios"; import LateralMenu from '../components/LateralMenu'; @@ -74,24 +73,24 @@ export default function Game() { } }, [gameId, generateQuestion]); - const answerButtonClick = (option) => { - setAnswer(question.answers[option-1]); - setSelectedOption((prevOption) => (prevOption === option ? null : option)); - const anyOptionSelected = option === selectedOption ? null : option; - setNextDisabled(anyOptionSelected === null); - }; + const answerButtonClick = (optionIndex) => { + const selectedOptionIndex = selectedOption === optionIndex ? null : optionIndex; + setSelectedOption(selectedOptionIndex); + const anyOptionSelected = selectedOptionIndex !== null; + setNextDisabled(!anyOptionSelected); + }; - const nextButtonClick = async () => { + const nextButtonClick = useCallback(async () => { try { const isCorrect = (await answerQuestion(gameId, answer.id)).wasCorrect; - + if (isCorrect) { setCorrectAnswers((prevCorrectAnswers) => prevCorrectAnswers + 1); setShowConfetti(true); } - + setSelectedOption(null); - + const nextRoundNumber = roundNumber + 1; if (nextRoundNumber > 9) navigate("/dashboard/game/results", { state: { correctAnswers: correctAnswers + (isCorrect ? 1 : 0) } }); @@ -104,7 +103,7 @@ export default function Game() { console.error("Error processing next question:", error); navigate("/dashboard"); } - }; + }, [gameId, answer.id, roundNumber, correctAnswers, generateQuestion, navigate]); useEffect(() => { let timeout; @@ -158,7 +157,16 @@ export default function Game() { {question.answers.map((answer, index) => ( - answerButtonClick(index + 1)} /> + ))} From 6beac8866c7853a27f303342695764ace2c4626c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Su=C3=A1rez=20Losada?= Date: Sat, 6 Apr 2024 19:15:46 +0200 Subject: [PATCH 019/118] feat: added feature for cucumber acceptance --- webapp/e2e/features/playing-game.feature | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 webapp/e2e/features/playing-game.feature diff --git a/webapp/e2e/features/playing-game.feature b/webapp/e2e/features/playing-game.feature new file mode 100644 index 00000000..d7d2da11 --- /dev/null +++ b/webapp/e2e/features/playing-game.feature @@ -0,0 +1,7 @@ +Feature: Starting a new game + +Scenario: A logged user wants to play a new game + Given A logged user + When Clicking the button to start a new game + Then A new game is created and the first question appears on the screen + From 5173295e36eb4953f57f58ae87b3f51d13e7e9b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Su=C3=A1rez=20Losada?= Date: Sat, 6 Apr 2024 19:32:55 +0200 Subject: [PATCH 020/118] feat: added feature for cucumber acceptance --- webapp/e2e/features/login.features | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 webapp/e2e/features/login.features diff --git a/webapp/e2e/features/login.features b/webapp/e2e/features/login.features new file mode 100644 index 00000000..482b23db --- /dev/null +++ b/webapp/e2e/features/login.features @@ -0,0 +1,7 @@ +Feature: Creating an account + +Scenario: A registered user wants to log in using his correct credentials + Given A registered user + When User enters the log in screen + And User writes his credentials properly + Then User is able to log in successfully From fe3e1f90883c1c8878ac00e8224f0aff44f91c47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Su=C3=A1rez=20Losada?= Date: Sat, 6 Apr 2024 19:34:36 +0200 Subject: [PATCH 021/118] feat: added feature for cucumber acceptance --- webapp/e2e/features/seeing-stats.feature | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 webapp/e2e/features/seeing-stats.feature diff --git a/webapp/e2e/features/seeing-stats.feature b/webapp/e2e/features/seeing-stats.feature new file mode 100644 index 00000000..1e85c965 --- /dev/null +++ b/webapp/e2e/features/seeing-stats.feature @@ -0,0 +1,5 @@ +Feature: Seeing the ladder board +Scenario: A logged user with many games wants to see its stats + Given A logged user with many games and a full ladder board (many other users with many other games) + When Clicking the button for seeing stats + Then it successfully displays both, the ladder board and the logged user statistics \ No newline at end of file From 9227e5024c89fc61f16ab817f9a407aa66f78a5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Su=C3=A1rez=20Losada?= Date: Sat, 6 Apr 2024 19:36:05 +0200 Subject: [PATCH 022/118] feat: added feature for cucumber acceptance --- webapp/e2e/features/register-form.feature | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webapp/e2e/features/register-form.feature b/webapp/e2e/features/register-form.feature index aad790a5..6917adf4 100644 --- a/webapp/e2e/features/register-form.feature +++ b/webapp/e2e/features/register-form.feature @@ -3,4 +3,4 @@ Feature: Registering a new user Scenario: The user is not registered in the site Given An unregistered user When I fill the data in the form and press submit - Then A confirmation message should be shown in the screen \ No newline at end of file + Then The main menu screen is shown and the new user is created \ No newline at end of file From 7d6226fa14bcf0293a81ccfbcf6bdd0cc3e19c32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Su=C3=A1rez=20Losada?= Date: Sat, 6 Apr 2024 19:36:25 +0200 Subject: [PATCH 023/118] fix: corrected Then feature for cucumber acceptance --- webapp/e2e/features/login.features | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webapp/e2e/features/login.features b/webapp/e2e/features/login.features index 482b23db..721fe32c 100644 --- a/webapp/e2e/features/login.features +++ b/webapp/e2e/features/login.features @@ -4,4 +4,4 @@ Scenario: A registered user wants to log in using his correct credentials Given A registered user When User enters the log in screen And User writes his credentials properly - Then User is able to log in successfully + Then The main menu screen shows on the user device From dc728b2f486fd528d33ff6e978951835ca7b858f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Su=C3=A1rez=20Losada?= Date: Sat, 6 Apr 2024 19:41:56 +0200 Subject: [PATCH 024/118] fix: corrected When feature for cucumber acceptance --- webapp/e2e/features/login.features | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/webapp/e2e/features/login.features b/webapp/e2e/features/login.features index 721fe32c..f85cac7f 100644 --- a/webapp/e2e/features/login.features +++ b/webapp/e2e/features/login.features @@ -1,7 +1,9 @@ Feature: Creating an account Scenario: A registered user wants to log in using his correct credentials - Given A registered user - When User enters the log in screen - And User writes his credentials properly + Given A registered user in the root screen + When User presses the log in button + And User enters in the log in screen + And User fills the form with his credentials properly + And User presses the log in button Then The main menu screen shows on the user device From 41580e4cd9a4f87dd1ade5d0b2c7812cbc7fe206 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Su=C3=A1rez=20Losada?= Date: Sat, 6 Apr 2024 19:42:06 +0200 Subject: [PATCH 025/118] fix: corrected When feature for cucumber acceptance --- webapp/e2e/features/seeing-stats.feature | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/webapp/e2e/features/seeing-stats.feature b/webapp/e2e/features/seeing-stats.feature index 1e85c965..7e2bec22 100644 --- a/webapp/e2e/features/seeing-stats.feature +++ b/webapp/e2e/features/seeing-stats.feature @@ -1,5 +1,8 @@ Feature: Seeing the ladder board + Scenario: A logged user with many games wants to see its stats - Given A logged user with many games and a full ladder board (many other users with many other games) - When Clicking the button for seeing stats + Given A logged user in the main menu with many games + And A full ladder board (many other users with many other games) + When The user presses the button for deploying the lateral menu + And the user presses the button for seeing stats Then it successfully displays both, the ladder board and the logged user statistics \ No newline at end of file From 31108ef9333739678b98fd0a1ecfa733f1a794cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Su=C3=A1rez=20Losada?= Date: Sat, 6 Apr 2024 19:42:20 +0200 Subject: [PATCH 026/118] fix: corrected Then feature for cucumber acceptance --- webapp/e2e/features/playing-game.feature | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webapp/e2e/features/playing-game.feature b/webapp/e2e/features/playing-game.feature index d7d2da11..1f4ae9ee 100644 --- a/webapp/e2e/features/playing-game.feature +++ b/webapp/e2e/features/playing-game.feature @@ -1,7 +1,7 @@ Feature: Starting a new game Scenario: A logged user wants to play a new game - Given A logged user + Given A logged user in the main menu When Clicking the button to start a new game Then A new game is created and the first question appears on the screen From 0852984e3bd7c58c8a3e2ee34322efad2c719dc3 Mon Sep 17 00:00:00 2001 From: Dario Date: Sat, 6 Apr 2024 20:14:02 +0200 Subject: [PATCH 027/118] fix: removed category and language from question --- .../java/lab/en2b/quizapi/questions/question/Question.java | 3 --- api/src/main/resources/application.properties | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/api/src/main/java/lab/en2b/quizapi/questions/question/Question.java b/api/src/main/java/lab/en2b/quizapi/questions/question/Question.java index 8ff15be0..17234423 100644 --- a/api/src/main/java/lab/en2b/quizapi/questions/question/Question.java +++ b/api/src/main/java/lab/en2b/quizapi/questions/question/Question.java @@ -39,9 +39,6 @@ public class Question { @Enumerated(EnumType.STRING) @Column(name = "question_category") private QuestionCategory questionCategory; - @Column(name = "answer_category") - private AnswerCategory answerCategory; - private String language; @Enumerated(EnumType.STRING) private QuestionType type; diff --git a/api/src/main/resources/application.properties b/api/src/main/resources/application.properties index 14b5bf17..c5fffc71 100644 --- a/api/src/main/resources/application.properties +++ b/api/src/main/resources/application.properties @@ -1,6 +1,6 @@ JWT_EXPIRATION_MS=86400000 REFRESH_TOKEN_DURATION_MS=86400000 -spring.jpa.hibernate.ddl-auto=validate +spring.jpa.hibernate.ddl-auto=update spring.datasource.url=${DATABASE_URL} spring.datasource.username=${DATABASE_USER} spring.datasource.password=${DATABASE_PASSWORD} From de190f9f60c867536ee882ee813b39566083f6e8 Mon Sep 17 00:00:00 2001 From: Dario Date: Sat, 6 Apr 2024 20:22:27 +0200 Subject: [PATCH 028/118] fix: removed attributes from tests --- api/src/test/java/lab/en2b/quizapi/game/GameServiceTest.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/api/src/test/java/lab/en2b/quizapi/game/GameServiceTest.java b/api/src/test/java/lab/en2b/quizapi/game/GameServiceTest.java index 97deeffb..b71bb584 100644 --- a/api/src/test/java/lab/en2b/quizapi/game/GameServiceTest.java +++ b/api/src/test/java/lab/en2b/quizapi/game/GameServiceTest.java @@ -88,9 +88,7 @@ void setUp() { .id(1L) .content("What is the capital of France?") .answers(new ArrayList<>()) - .language("en") .questionCategory(QuestionCategory.GEOGRAPHY) - .answerCategory(AnswerCategory.CAPITAL_CITY) .type(QuestionType.TEXT) .build(); From 37a1620665a1666b8906c9867318405480d56113 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Su=C3=A1rez=20Losada?= Date: Sat, 6 Apr 2024 20:39:00 +0200 Subject: [PATCH 029/118] fix: reorganized structure for login feature (acceptance tests) --- .../{login.features => login_features/positive_login.features} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename webapp/e2e/features/{login.features => login_features/positive_login.features} (100%) diff --git a/webapp/e2e/features/login.features b/webapp/e2e/features/login_features/positive_login.features similarity index 100% rename from webapp/e2e/features/login.features rename to webapp/e2e/features/login_features/positive_login.features From 13ade404ce5eec4d45dc6cc9d9c61db2aa256088 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Su=C3=A1rez=20Losada?= Date: Sat, 6 Apr 2024 20:58:56 +0200 Subject: [PATCH 030/118] feat: added feature for blank email login (acceptance tests) --- .../login_features/negative_blank_email_login.features | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 webapp/e2e/features/login_features/negative_blank_email_login.features diff --git a/webapp/e2e/features/login_features/negative_blank_email_login.features b/webapp/e2e/features/login_features/negative_blank_email_login.features new file mode 100644 index 00000000..83795c21 --- /dev/null +++ b/webapp/e2e/features/login_features/negative_blank_email_login.features @@ -0,0 +1,9 @@ +Feature: Preventing wrong login accesses + +Scenario: A registered user wants to log in using his credentials but leaving the password in blank + Given A registered user in the root screen + When User presses the log in button + And User enters in the log in screen + And User fills the form with his proper email but leaves the password in blank + And User presses the log in button + Then Log in screen shows an informative error message and does not allow the user to log in \ No newline at end of file From 1474009e9124b3c2ac7d878f7e2e8c6188b9dd36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Su=C3=A1rez=20Losada?= Date: Sat, 6 Apr 2024 20:59:54 +0200 Subject: [PATCH 031/118] feat: added feature for blank password login (acceptance tests) --- .../negative_blank_password_login.features | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 webapp/e2e/features/login_features/negative_blank_password_login.features diff --git a/webapp/e2e/features/login_features/negative_blank_password_login.features b/webapp/e2e/features/login_features/negative_blank_password_login.features new file mode 100644 index 00000000..2239ea5f --- /dev/null +++ b/webapp/e2e/features/login_features/negative_blank_password_login.features @@ -0,0 +1,9 @@ +Feature: Preventing wrong login accesses + +Scenario: A registered user wants to log in using his credentials but leaving the email in blank + Given A registered user in the root screen + When User presses the log in button + And User enters in the log in screen + And User fills the form with his proper password but leaves the email in blank + And User presses the log in button + Then Log in screen shows an informative error message and does not allow the user to log in \ No newline at end of file From c007141472e98dec42658ef35188bace7a44d5c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Su=C3=A1rez=20Losada?= Date: Sat, 6 Apr 2024 21:03:16 +0200 Subject: [PATCH 032/118] feat: added feature for blank email login (acceptance tests) --- .../negative_incorrect_credentials.feature | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 webapp/e2e/features/login_features/negative_incorrect_credentials.feature diff --git a/webapp/e2e/features/login_features/negative_incorrect_credentials.feature b/webapp/e2e/features/login_features/negative_incorrect_credentials.feature new file mode 100644 index 00000000..662052c5 --- /dev/null +++ b/webapp/e2e/features/login_features/negative_incorrect_credentials.feature @@ -0,0 +1,9 @@ +Feature: Preventing wrong login accesses + + Scenario: A registered user wants to log in using his credentials but leaving the email in blank + Given A registered user in the root screen + When User presses the log in button + And User enters in the log in screen + And User fills the form with his proper password but leaves the email in blank + And User presses the log in button + Then Log in screen shows an informative error message and does not allow the user to log in \ No newline at end of file From d9d1691f87a43e7c172cf51e838912f546a96078 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Su=C3=A1rez=20Losada?= Date: Sat, 6 Apr 2024 21:07:11 +0200 Subject: [PATCH 033/118] fix: solved typo in login features (acceptance tests) --- ...nk_email_login.features => negative_blank_email_login.feature} | 0 ...sword_login.features => negative_blank_password_login.feature} | 0 .../{positive_login.features => positive_login.feature} | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename webapp/e2e/features/login_features/{negative_blank_email_login.features => negative_blank_email_login.feature} (100%) rename webapp/e2e/features/login_features/{negative_blank_password_login.features => negative_blank_password_login.feature} (100%) rename webapp/e2e/features/login_features/{positive_login.features => positive_login.feature} (100%) diff --git a/webapp/e2e/features/login_features/negative_blank_email_login.features b/webapp/e2e/features/login_features/negative_blank_email_login.feature similarity index 100% rename from webapp/e2e/features/login_features/negative_blank_email_login.features rename to webapp/e2e/features/login_features/negative_blank_email_login.feature diff --git a/webapp/e2e/features/login_features/negative_blank_password_login.features b/webapp/e2e/features/login_features/negative_blank_password_login.feature similarity index 100% rename from webapp/e2e/features/login_features/negative_blank_password_login.features rename to webapp/e2e/features/login_features/negative_blank_password_login.feature diff --git a/webapp/e2e/features/login_features/positive_login.features b/webapp/e2e/features/login_features/positive_login.feature similarity index 100% rename from webapp/e2e/features/login_features/positive_login.features rename to webapp/e2e/features/login_features/positive_login.feature From 1ef3b1db5c942e4e3e8ef6e92fde1a731de7b0ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Su=C3=A1rez=20Losada?= Date: Sat, 6 Apr 2024 21:09:35 +0200 Subject: [PATCH 034/118] feat: added feature for bad email formatting in login screen (acceptance tests) --- .../negative_bad_format_email_login.feature | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 webapp/e2e/features/login_features/negative_bad_format_email_login.feature diff --git a/webapp/e2e/features/login_features/negative_bad_format_email_login.feature b/webapp/e2e/features/login_features/negative_bad_format_email_login.feature new file mode 100644 index 00000000..fde5ef2f --- /dev/null +++ b/webapp/e2e/features/login_features/negative_bad_format_email_login.feature @@ -0,0 +1,9 @@ +Feature: Preventing wrong login accesses + + Scenario: A registered user wants to log in using his credentials but with an invalid email + Given A registered user in the root screen + When User presses the log in button + And User enters in the log in screen + And User fills the form with his proper password but writes a wrong formatted email + And User presses the log in button + Then Log in screen shows an informative error message and does not allow the user to log in \ No newline at end of file From 2aebcfbe0caee935b4ce87e989ebd9b0241925a4 Mon Sep 17 00:00:00 2001 From: sergioqfeg1 Date: Sat, 6 Apr 2024 21:10:40 +0200 Subject: [PATCH 035/118] fix: new round conditions --- api/src/main/java/lab/en2b/quizapi/game/Game.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/main/java/lab/en2b/quizapi/game/Game.java b/api/src/main/java/lab/en2b/quizapi/game/Game.java index 5028b9d7..cd928e0e 100644 --- a/api/src/main/java/lab/en2b/quizapi/game/Game.java +++ b/api/src/main/java/lab/en2b/quizapi/game/Game.java @@ -54,7 +54,7 @@ public void newRound(Question question){ if(getActualRound() != 0){ if (isGameOver()) throw new IllegalStateException("You can't start a round for a finished game!"); - if(!currentRoundIsOver()) + if(!currentQuestionAnswered || roundTimeHasExpired()) throw new IllegalStateException("You can't start a new round when the current round is not over yet!"); } From 94626aca56a0b37a2a5a25ba0f818187587198c0 Mon Sep 17 00:00:00 2001 From: Gonzalo Alonso Fernandez Date: Sat, 6 Apr 2024 21:16:53 +0200 Subject: [PATCH 036/118] feat: changiong the game implementation. --- api/src/main/resources/application.properties | 2 +- webapp/src/pages/Game.jsx | 21 ++++++++++++------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/api/src/main/resources/application.properties b/api/src/main/resources/application.properties index c5fffc71..8f7af9da 100644 --- a/api/src/main/resources/application.properties +++ b/api/src/main/resources/application.properties @@ -1,6 +1,6 @@ JWT_EXPIRATION_MS=86400000 REFRESH_TOKEN_DURATION_MS=86400000 -spring.jpa.hibernate.ddl-auto=update +spring.jpa.hibernate.ddl-auto=create spring.datasource.url=${DATABASE_URL} spring.datasource.username=${DATABASE_USER} spring.datasource.password=${DATABASE_PASSWORD} diff --git a/webapp/src/pages/Game.jsx b/webapp/src/pages/Game.jsx index b0282b7f..aad355b4 100644 --- a/webapp/src/pages/Game.jsx +++ b/webapp/src/pages/Game.jsx @@ -15,7 +15,7 @@ export default function Game() { const [loading, setLoading] = useState(true); const [gameId, setGameId] = useState(null); const [question, setQuestion] = useState(null); - const [answer, setAnswer] = useState({id:1, text:"answer1", category:"category1" }); + const [answer, setAnswer] = useState({}); const [selectedOption, setSelectedOption] = useState(null); const [nextDisabled, setNextDisabled] = useState(true); const [roundNumber, setRoundNumber] = useState(1); @@ -69,16 +69,19 @@ export default function Game() { useEffect(() => { if (gameId !== null) { + setSelectedOption(null); generateQuestion(); } }, [gameId, generateQuestion]); + - const answerButtonClick = (optionIndex) => { + const answerButtonClick = (optionIndex, answer) => { const selectedOptionIndex = selectedOption === optionIndex ? null : optionIndex; setSelectedOption(selectedOptionIndex); + setAnswer(answer); const anyOptionSelected = selectedOptionIndex !== null; setNextDisabled(!anyOptionSelected); - }; + }; const nextButtonClick = useCallback(async () => { try { @@ -95,9 +98,11 @@ export default function Game() { if (nextRoundNumber > 9) navigate("/dashboard/game/results", { state: { correctAnswers: correctAnswers + (isCorrect ? 1 : 0) } }); else { + setAnswer({}); setRoundNumber(nextRoundNumber); setNextDisabled(true); await generateQuestion(); + startRound(gameId); } } catch (error) { console.error("Error processing next question:", error); @@ -157,12 +162,12 @@ export default function Game() { {question.answers.map((answer, index) => ( - From 334e6bc6cec953695209c12cbce006403f6f7efa Mon Sep 17 00:00:00 2001 From: Dario Date: Mon, 8 Apr 2024 21:48:52 +0200 Subject: [PATCH 111/118] fix: game over again --- api/src/main/java/lab/en2b/quizapi/game/Game.java | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/api/src/main/java/lab/en2b/quizapi/game/Game.java b/api/src/main/java/lab/en2b/quizapi/game/Game.java index fa75cf5c..e8ae10c9 100644 --- a/api/src/main/java/lab/en2b/quizapi/game/Game.java +++ b/api/src/main/java/lab/en2b/quizapi/game/Game.java @@ -5,7 +5,6 @@ import lab.en2b.quizapi.commons.user.User; import lab.en2b.quizapi.questions.answer.Answer; import lab.en2b.quizapi.questions.question.Question; -import lab.en2b.quizapi.questions.question.QuestionRepository; import lombok.*; import java.time.LocalDateTime; @@ -71,12 +70,9 @@ private void increaseRound(){ } public boolean isGameOver(){ - return isGameOver && getActualRound() > getRounds(); + return isGameOver && getActualRound() >= getRounds(); } - public boolean isLastRound(){ - return getActualRound() > getRounds(); - } public Question getCurrentQuestion() { if(getRoundStartTime() == null){ @@ -97,7 +93,7 @@ private boolean roundTimeHasExpired(){ return getRoundStartTime()!= null && LocalDateTime.now().isAfter(getRoundStartTime().plusSeconds(getRoundDuration())); } - public void answerQuestion(Long answerId, QuestionRepository questionRepository){ + public void answerQuestion(Long answerId){ if(currentRoundIsOver()) throw new IllegalStateException("You can't answer a question when the current round is over!"); if (isGameOver()) @@ -121,6 +117,6 @@ private boolean isLanguageSupported(String language) { } public boolean shouldBeGameOver() { - return getActualRound() > getRounds() && !isGameOver; + return getActualRound() >= getRounds() && !isGameOver; } } From aa0b499ff63f1e3208f9e28be3b0c7444352f543 Mon Sep 17 00:00:00 2001 From: Dario Date: Mon, 8 Apr 2024 21:57:07 +0200 Subject: [PATCH 112/118] fix: answer now returns boolean --- api/src/main/java/lab/en2b/quizapi/game/Game.java | 3 ++- .../java/lab/en2b/quizapi/game/GameController.java | 3 ++- .../java/lab/en2b/quizapi/game/GameService.java | 7 ++++--- .../quizapi/game/dtos/AnswerGameResponseDto.java | 14 ++++++++++++++ webapp/src/pages/Game.jsx | 4 ++-- 5 files changed, 24 insertions(+), 7 deletions(-) create mode 100644 api/src/main/java/lab/en2b/quizapi/game/dtos/AnswerGameResponseDto.java diff --git a/api/src/main/java/lab/en2b/quizapi/game/Game.java b/api/src/main/java/lab/en2b/quizapi/game/Game.java index e8ae10c9..579f0346 100644 --- a/api/src/main/java/lab/en2b/quizapi/game/Game.java +++ b/api/src/main/java/lab/en2b/quizapi/game/Game.java @@ -93,7 +93,7 @@ private boolean roundTimeHasExpired(){ return getRoundStartTime()!= null && LocalDateTime.now().isAfter(getRoundStartTime().plusSeconds(getRoundDuration())); } - public void answerQuestion(Long answerId){ + public boolean answerQuestion(Long answerId){ if(currentRoundIsOver()) throw new IllegalStateException("You can't answer a question when the current round is over!"); if (isGameOver()) @@ -105,6 +105,7 @@ public void answerQuestion(Long answerId){ setCorrectlyAnsweredQuestions(getCorrectlyAnsweredQuestions() + 1); } setCurrentQuestionAnswered(true); + return q.isCorrectAnswer(answerId); } public void setLanguage(String language){ if(!isLanguageSupported(language)) diff --git a/api/src/main/java/lab/en2b/quizapi/game/GameController.java b/api/src/main/java/lab/en2b/quizapi/game/GameController.java index 2f5e2d7e..c39409ae 100644 --- a/api/src/main/java/lab/en2b/quizapi/game/GameController.java +++ b/api/src/main/java/lab/en2b/quizapi/game/GameController.java @@ -3,6 +3,7 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; +import lab.en2b.quizapi.game.dtos.AnswerGameResponseDto; import lab.en2b.quizapi.game.dtos.GameAnswerDto; import lab.en2b.quizapi.game.dtos.GameResponseDto; import lab.en2b.quizapi.questions.question.QuestionCategory; @@ -57,7 +58,7 @@ public ResponseEntity getCurrentQuestion(@PathVariable Long @ApiResponse(responseCode = "403", description = "You are not logged in", content = @io.swagger.v3.oas.annotations.media.Content), }) @PostMapping("/{id}/answer") - public ResponseEntity answerQuestion(@PathVariable Long id, @RequestBody GameAnswerDto dto, Authentication authentication){ + public ResponseEntity answerQuestion(@PathVariable Long id, @RequestBody GameAnswerDto dto, Authentication authentication){ return ResponseEntity.ok(gameService.answerQuestion(id, dto, authentication)); } diff --git a/api/src/main/java/lab/en2b/quizapi/game/GameService.java b/api/src/main/java/lab/en2b/quizapi/game/GameService.java index 11ea96c3..ab75b519 100644 --- a/api/src/main/java/lab/en2b/quizapi/game/GameService.java +++ b/api/src/main/java/lab/en2b/quizapi/game/GameService.java @@ -1,6 +1,7 @@ package lab.en2b.quizapi.game; import lab.en2b.quizapi.commons.user.UserService; +import lab.en2b.quizapi.game.dtos.AnswerGameResponseDto; import lab.en2b.quizapi.game.dtos.GameAnswerDto; import lab.en2b.quizapi.game.dtos.GameResponseDto; import lab.en2b.quizapi.game.mappers.GameResponseDtoMapper; @@ -74,9 +75,9 @@ public QuestionResponseDto getCurrentQuestion(Long id, Authentication authentica } @Transactional - public GameResponseDto answerQuestion(Long id, GameAnswerDto dto, Authentication authentication){ + public AnswerGameResponseDto answerQuestion(Long id, GameAnswerDto dto, Authentication authentication){ Game game = gameRepository.findByIdForUser(id, userService.getUserByAuthentication(authentication).getId()).orElseThrow(); - game.answerQuestion(dto.getAnswerId(), questionRepository); + boolean wasCorrect = game.answerQuestion(dto.getAnswerId()); if (game.shouldBeGameOver()){ game.setGameOver(true); @@ -84,7 +85,7 @@ public GameResponseDto answerQuestion(Long id, GameAnswerDto dto, Authentication saveStatistics(game); } - return gameResponseDtoMapper.apply(game); + return new AnswerGameResponseDto(wasCorrect); } private void saveStatistics(Game game){ if (statisticsRepository.findByUserId(game.getUser().getId()).isPresent()){ diff --git a/api/src/main/java/lab/en2b/quizapi/game/dtos/AnswerGameResponseDto.java b/api/src/main/java/lab/en2b/quizapi/game/dtos/AnswerGameResponseDto.java new file mode 100644 index 00000000..9d9d3955 --- /dev/null +++ b/api/src/main/java/lab/en2b/quizapi/game/dtos/AnswerGameResponseDto.java @@ -0,0 +1,14 @@ +package lab.en2b.quizapi.game.dtos; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@AllArgsConstructor +@Data +@NoArgsConstructor +public class AnswerGameResponseDto { + @JsonProperty("was_correct") + private boolean wasCorrect; +} diff --git a/webapp/src/pages/Game.jsx b/webapp/src/pages/Game.jsx index 4d9013ed..5fcc6b28 100644 --- a/webapp/src/pages/Game.jsx +++ b/webapp/src/pages/Game.jsx @@ -112,7 +112,7 @@ export default function Game() { const nextButtonClick = useCallback(async () => { try { - const isCorrect = (await answerQuestion(gameId, answer.id)).correctly_answered_questions; + const isCorrect = (await answerQuestion(gameId, answer.id)).was_correct; if (isCorrect) { setCorrectAnswers(correctAnswers + (isCorrect ? 1 : 0)); @@ -231,7 +231,7 @@ export default function Game() { From 3d9bbb24fe559bcceffdac9518541f427efadb6f Mon Sep 17 00:00:00 2001 From: Dario Date: Mon, 8 Apr 2024 22:00:04 +0200 Subject: [PATCH 113/118] fix: answer now returns boolean --- webapp/public/locales/en/translation.json | 2 +- webapp/public/locales/es/translation.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/webapp/public/locales/en/translation.json b/webapp/public/locales/en/translation.json index 07f9238f..b61195dc 100644 --- a/webapp/public/locales/en/translation.json +++ b/webapp/public/locales/en/translation.json @@ -75,7 +75,7 @@ }, "game": { "round": "Round ", - "next": "Next" + "answer": "Answer" }, "about": { "title": "About", diff --git a/webapp/public/locales/es/translation.json b/webapp/public/locales/es/translation.json index f854287b..a67bc786 100644 --- a/webapp/public/locales/es/translation.json +++ b/webapp/public/locales/es/translation.json @@ -74,7 +74,7 @@ }, "game": { "round": "Ronda ", - "next": "Siguiente" + "answer": "Responder" }, "about": { "title": "Sobre nosotros", From 2d660297bf315788dca5ccc89babb04d4cdf8df0 Mon Sep 17 00:00:00 2001 From: Dario Date: Mon, 8 Apr 2024 22:18:37 +0200 Subject: [PATCH 114/118] fix: user stats in game --- webapp/src/pages/Results.jsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/webapp/src/pages/Results.jsx b/webapp/src/pages/Results.jsx index 3abbd0cb..3d934288 100644 --- a/webapp/src/pages/Results.jsx +++ b/webapp/src/pages/Results.jsx @@ -2,6 +2,7 @@ import React from "react"; import { useTranslation } from "react-i18next"; import { Button, Flex, Box, Heading, Center } from "@chakra-ui/react"; import { useNavigate, useLocation } from "react-router-dom"; +import UserStatistics from "../components/statistics/UserStatistics"; export default function Results() { const { t } = useTranslation(); @@ -19,6 +20,7 @@ export default function Results() { {t("common.finish")} + ); From 41dc7f616c5110ac8bdc22971fe3f523e6d56c97 Mon Sep 17 00:00:00 2001 From: jjgancfer Date: Mon, 8 Apr 2024 22:36:22 +0200 Subject: [PATCH 115/118] feat: add a config to tests that work --- ...ve_logged_user_seeing_about_screen.feature | 4 +- webapp/e2e/jest.config.js | 4 +- webapp/e2e/steps/about.steps.js | 60 +++++++++++++++++++ webapp/e2e/steps/register-form.steps.js | 52 ---------------- webapp/e2e/test-environment-setup.js | 19 ------ webapp/package.json | 2 +- webapp/src/components/LateralMenu.jsx | 2 +- webapp/src/components/MenuButton.jsx | 2 +- webapp/src/pages/About.jsx | 3 +- 9 files changed, 68 insertions(+), 80 deletions(-) create mode 100644 webapp/e2e/steps/about.steps.js delete mode 100644 webapp/e2e/steps/register-form.steps.js delete mode 100644 webapp/e2e/test-environment-setup.js diff --git a/webapp/e2e/features/about_features/positive_logged_user_seeing_about_screen.feature b/webapp/e2e/features/about_features/positive_logged_user_seeing_about_screen.feature index b75ae049..63d9fa6c 100644 --- a/webapp/e2e/features/about_features/positive_logged_user_seeing_about_screen.feature +++ b/webapp/e2e/features/about_features/positive_logged_user_seeing_about_screen.feature @@ -3,5 +3,5 @@ Feature: Seeing the about screen of the webpage Scenario: A logged user wants to see the about screen of the webpage Given A logged user in the main menu When The user presses the button for deploying the lateral menu - And the user presses the button for seeing the about secction (i) - Then The screen shows redirects the user to the about screen \ No newline at end of file + And the user presses the button for seeing the about section (i) + Then The user is presented to the about screen \ No newline at end of file diff --git a/webapp/e2e/jest.config.js b/webapp/e2e/jest.config.js index db3be3d9..147c0817 100644 --- a/webapp/e2e/jest.config.js +++ b/webapp/e2e/jest.config.js @@ -1,5 +1,5 @@ module.exports = { testMatch: ["**/steps/*.js"], - testTimeout: 30000, - setupFilesAfterEnv: ["expect-puppeteer"] + moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"], + testTimeout: 30000 } \ No newline at end of file diff --git a/webapp/e2e/steps/about.steps.js b/webapp/e2e/steps/about.steps.js new file mode 100644 index 00000000..4730db3a --- /dev/null +++ b/webapp/e2e/steps/about.steps.js @@ -0,0 +1,60 @@ +const { defineFeature, loadFeature }=require('jest-cucumber'); +const puppeteer = require('puppeteer'); +const setDefaultOptions = require("expect-puppeteer").setDefaultOptions; +const feature = loadFeature('./features/about_features/positive_logged_user_seeing_about_screen.feature'); +let page; +let browser; + +defineFeature(feature, test => { + + beforeAll(async () => { + browser = process.env.GITHUB_ACTIONS + ? await puppeteer.launch() + : await puppeteer.launch({ headless: false, slowMo: 100 }); + page = await browser.newPage(); + //Way of setting up the timeout + setDefaultOptions({ timeout: 10000 }) + + await page + .goto("http://localhost:3000", { + waitUntil: "networkidle0", + }) + .catch(() => {}); + }); + + test("A logged user wants to see the about screen of the webpage", ({given,when,and,then}) => { + + let username; + let password; + + given("A logged user in the main menu", async () => { + username = "test@email.com" + password = "password" + + await expect(page).toClick("button[data-testid='Login'"); + await expect(page).toFill("#user", username); + await expect(page).toFill("#password", password); + await expect(page).toClick("button[data-testid='Login'"); + }); + + when("The user presses the button for deploying the lateral menu", async () => { + await expect(page).toClick("#lateralMenuButton"); + }); + + and("the user presses the button for seeing the about section (i)", async () => { + await expect(page).toClick("#aboutButton"); + }); + + then("The user is presented to the about screen", async () => { + let header = await page.$eval("h2", (element) => { + return element.innerHTML + }) + let value = header === "About" || header === "Sobre nosotros"; + expect(value).toBeTruthy(); + }); + }); + + afterAll((done) => { + done(); + }); +}); \ No newline at end of file diff --git a/webapp/e2e/steps/register-form.steps.js b/webapp/e2e/steps/register-form.steps.js deleted file mode 100644 index 172e1969..00000000 --- a/webapp/e2e/steps/register-form.steps.js +++ /dev/null @@ -1,52 +0,0 @@ -const puppeteer = require('puppeteer'); -const { defineFeature, loadFeature }=require('jest-cucumber'); -const setDefaultOptions = require('expect-puppeteer').setDefaultOptions -const feature = loadFeature('./features/register-form.feature'); - -let page; -let browser; - -defineFeature(feature, test => { - - beforeAll(async () => { - browser = process.env.GITHUB_ACTIONS - ? await puppeteer.launch() - : await puppeteer.launch({ headless: false, slowMo: 100 }); - page = await browser.newPage(); - //Way of setting up the timeout - setDefaultOptions({ timeout: 10000 }) - - await page - .goto("http://localhost:3000", { - waitUntil: "networkidle0", - }) - .catch(() => {}); - }); - - test('The user is not registered in the site', ({given,when,then}) => { - - let username; - let password; - - given('An unregistered user', async () => { - username = "pablo" - password = "pabloasw" - await expect(page).toClick("button", { text: "Don't have an account? Register here." }); - }); - - when('I fill the data in the form and press submit', async () => { - await expect(page).toFill('input[name="username"]', username); - await expect(page).toFill('input[name="password"]', password); - await expect(page).toClick('button', { text: 'Add User' }) - }); - - then('A confirmation message should be shown in the screen', async () => { - await expect(page).toMatchElement("div", { text: "User added successfully" }); - }); - }) - - afterAll(async ()=>{ - browser.close() - }) - -}); \ No newline at end of file diff --git a/webapp/e2e/test-environment-setup.js b/webapp/e2e/test-environment-setup.js deleted file mode 100644 index 7b7ed511..00000000 --- a/webapp/e2e/test-environment-setup.js +++ /dev/null @@ -1,19 +0,0 @@ -const { MongoMemoryServer } = require('mongodb-memory-server'); - - -let mongoserver; -let userservice; -let authservice; -let gatewayservice; - -async function startServer() { - console.log('Starting MongoDB memory server...'); - mongoserver = await MongoMemoryServer.create(); - const mongoUri = mongoserver.getUri(); - process.env.MONGODB_URI = mongoUri; - userservice = await require("../../users/userservice/user-service"); - authservice = await require("../../users/authservice/auth-service"); - gatewayservice = await require("../../gatewayservice/gateway-service"); - } - - startServer(); diff --git a/webapp/package.json b/webapp/package.json index 00f78c59..7b1b35ac 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -35,7 +35,7 @@ "build": "react-scripts build", "prod": "serve -s build", "test": "react-scripts test --transformIgnorePatterns 'node_modules/(?!axios)/'", - "test:e2e": "start-server-and-test 'node e2e/test-environment-setup.js' http://localhost:8000/health prod 3000 \"cd e2e && jest\"", + "test:e2e": "start-server-and-test start 3000 \"cd e2e && jest --detectOpenHandles\"", "eject": "react-scripts eject" }, "eslintConfig": { diff --git a/webapp/src/components/LateralMenu.jsx b/webapp/src/components/LateralMenu.jsx index c9066075..b6a63379 100644 --- a/webapp/src/components/LateralMenu.jsx +++ b/webapp/src/components/LateralMenu.jsx @@ -103,7 +103,7 @@ const LateralMenu = ({ isOpen, onClose, changeLanguage, isDashboard }) => { {isLoggedIn && ( )} - } className={"custom-button effect1"} onClick={() => {navigate("/about");}} margin={"10px"}> + } className={"custom-button effect1"} onClick={() => {navigate("/about");}} margin={"10px"} id={"aboutButton"}> diff --git a/webapp/src/components/MenuButton.jsx b/webapp/src/components/MenuButton.jsx index 73e5a3e7..8c4da367 100644 --- a/webapp/src/components/MenuButton.jsx +++ b/webapp/src/components/MenuButton.jsx @@ -22,7 +22,7 @@ const MenuButton = ({ onClick }) => { }, []); return ( - + {t('about.title')} - + {t("about.description1")} -

From 60b2faf044035d042b2b61fbcb8196720b15f7e0 Mon Sep 17 00:00:00 2001 From: Dario Date: Mon, 8 Apr 2024 22:47:41 +0200 Subject: [PATCH 116/118] fix: question not loading --- webapp/src/components/game/Game.js | 9 +-------- webapp/src/pages/Game.jsx | 31 +++++++++++++++--------------- 2 files changed, 17 insertions(+), 23 deletions(-) diff --git a/webapp/src/components/game/Game.js b/webapp/src/components/game/Game.js index 2c84c647..ffe3ac04 100644 --- a/webapp/src/components/game/Game.js +++ b/webapp/src/components/game/Game.js @@ -34,14 +34,7 @@ export async function changeLanguage(gameId, language) { } export async function answerQuestion(gameId, aId) { - try { - let requestAnswer = await authManager.getAxiosInstance().post(process.env.REACT_APP_API_ENDPOINT + "/games/" + gameId + "/answer", {answer_id:aId}); - if (HttpStatusCode.Ok === requestAnswer.status) { - return requestAnswer.data; - } - } catch { - - } + return await authManager.getAxiosInstance().post(process.env.REACT_APP_API_ENDPOINT + "/games/" + gameId + "/answer", {answer_id:aId}); } export async function getGameDetails(gameId) { diff --git a/webapp/src/pages/Game.jsx b/webapp/src/pages/Game.jsx index 5fcc6b28..6e719e7c 100644 --- a/webapp/src/pages/Game.jsx +++ b/webapp/src/pages/Game.jsx @@ -79,7 +79,7 @@ export default function Game() { /* Generate new question when the round changes */ - const assignQuestion = useCallback(async () => { + const assignQuestion = useCallback(async (gameId) => { try { const result = await getCurrentQuestion(gameId); if (result.status === 200) { @@ -87,12 +87,11 @@ export default function Game() { await setQuestionLoading(false); setTimeElapsed(0); } else { - console.log(result) - //navigate("/dashboard"); + navigate("/dashboard"); } } catch (error) { console.error("Error fetching question:", error); - //navigate("/dashboard"); + navigate("/dashboard"); } }, [gameId, navigate]); useEffect(() => { @@ -102,29 +101,31 @@ export default function Game() { } }, [gameId, assignQuestion]); - const answerButtonClick = (optionIndex, answer) => { + const answerButtonClick = async (optionIndex, answer) => { const selectedOptionIndex = selectedOption === optionIndex ? null : optionIndex; setSelectedOption(selectedOptionIndex); - setAnswer(answer); + await setAnswer(answer); const anyOptionSelected = selectedOptionIndex !== null; setNextDisabled(!anyOptionSelected); }; const nextButtonClick = useCallback(async () => { try { - const isCorrect = (await answerQuestion(gameId, answer.id)).was_correct; - + const result = await answerQuestion(gameId, answer.id); + let isCorrect = result.data.was_correct; if (isCorrect) { setCorrectAnswers(correctAnswers + (isCorrect ? 1 : 0)); setShowConfetti(true); } - setSelectedOption(null); await nextRound() } catch (error) { - console.error("Error processing next question:", error); - navigate("/dashboard"); + if(error.response.status === 400){ + setTimeout(nextButtonClick, 2000) + }else{ + console.log('xd'+error.status) + } } }, [gameId, answer.id, roundNumber, correctAnswers, assignQuestion, navigate]); @@ -139,7 +140,7 @@ export default function Game() { setQuestionLoading(true); await startNewRound(gameId); - await assignQuestion(); + await assignQuestion(gameId); } }, [gameId, answer.id, roundNumber, correctAnswers, assignQuestion, navigate]); @@ -150,14 +151,14 @@ export default function Game() { setTimeStartRound(new Date(result.data.round_start_time).getTime()); setRoundNumber(result.data.actual_round ) setRoundDuration(result.data.round_duration); - await assignQuestion(); + await assignQuestion(gameId); } catch(error){ if(error.status === 409){ if(roundNumber >= 9){ navigate("/dashboard/game/results", { state: { correctAnswers: correctAnswers } }); } else { - await assignQuestion() + await assignQuestion(gameId) } } @@ -186,7 +187,7 @@ export default function Game() { }, 1000); } return () => clearTimeout(timeout); - }, [timeElapsed, nextButtonClick]); + }, [timeElapsed]); return ( From 1aa45df2285876d7e5a4e13e9c59e8dc4bcdc0db Mon Sep 17 00:00:00 2001 From: Dario Date: Mon, 8 Apr 2024 22:53:21 +0200 Subject: [PATCH 117/118] fix: loading after loading questions --- webapp/src/pages/Game.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/webapp/src/pages/Game.jsx b/webapp/src/pages/Game.jsx index 6e719e7c..918c4a61 100644 --- a/webapp/src/pages/Game.jsx +++ b/webapp/src/pages/Game.jsx @@ -46,7 +46,6 @@ export default function Game() { try { const newGameResponse = await newGame(); if (newGameResponse) { - setLoading(false); setRoundNumber(newGameResponse.actual_round) await setGameId(newGameResponse.id); setTimeStartRound(new Date(newGameResponse.round_start_time).getTime()); @@ -62,7 +61,7 @@ export default function Game() { }catch (error) { await startNewRound(newGameResponse.id); } - + setLoading(false); } else { navigate("/dashboard"); } @@ -154,6 +153,7 @@ export default function Game() { await assignQuestion(gameId); } catch(error){ + console.log(error) if(error.status === 409){ if(roundNumber >= 9){ navigate("/dashboard/game/results", { state: { correctAnswers: correctAnswers } }); From 61d4ffb673f85e437883403df2f1c0193df98faa Mon Sep 17 00:00:00 2001 From: jjgancfer Date: Mon, 8 Apr 2024 23:08:23 +0200 Subject: [PATCH 118/118] chore: restore e2e --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b082ddb8..dc6639b7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -41,7 +41,7 @@ jobs: node-version: 20 - run: npm --prefix webapp install - run: npm --prefix webapp run build - #- run: npm --prefix webapp run test:e2e TODO: re-enable + - run: npm --prefix webapp run test:e2e docker-push-api: runs-on: ubuntu-latest needs: [ e2e-tests ]