From 0b467300bfd490ce5f8b32f05d65c4e4946ffe05 Mon Sep 17 00:00:00 2001
From: sergiorodriguezgarcia
<113514397+sergiorodriguezgarcia@users.noreply.github.com>
Date: Mon, 22 Apr 2024 17:10:40 +0200
Subject: [PATCH 1/9] feat: Adding descriptive error messages
---
webapp/public/locales/en/translation.json | 6 +++---
webapp/public/locales/es/translation.json | 6 +++---
webapp/src/components/auth/AuthManager.js | 17 +++--------------
webapp/src/pages/Login.jsx | 19 +++++++++++++++++--
webapp/src/pages/Signup.jsx | 9 +++++----
5 files changed, 31 insertions(+), 26 deletions(-)
diff --git a/webapp/public/locales/en/translation.json b/webapp/public/locales/en/translation.json
index b62b16d6..5871cfa4 100644
--- a/webapp/public/locales/en/translation.json
+++ b/webapp/public/locales/en/translation.json
@@ -40,11 +40,11 @@
"login-send": "Your email or password are not found in our database",
"validation": {
"type": "Validation Error: ",
- "message": "Incorrect data"
+ "message": "Invalid email format"
},
"conflict": {
"type": "Conflict Error: ",
- "message": "User already exists"
+ "message": "Invalid email format or credentials (username or email) already in use"
},
"unknown": {
"type": "Unknown Error",
@@ -52,7 +52,7 @@
},
"authorized": {
"type": "Authorization Error: ",
- "message": "Incorrect password"
+ "message": "Invalid email or password, check for them to be correct"
}
},
"rules": {
diff --git a/webapp/public/locales/es/translation.json b/webapp/public/locales/es/translation.json
index 8cfdd594..32c37486 100644
--- a/webapp/public/locales/es/translation.json
+++ b/webapp/public/locales/es/translation.json
@@ -40,11 +40,11 @@
"login-send": "Tu email o contraseña no se encuentran en nuestra base de datos",
"validation": {
"type": "Error de Validación: ",
- "message": "Datos incorrectos"
+ "message": "El formato del correo electrónico no es correcto"
},
"conflict": {
"type": "Error de Conflicto: ",
- "message": "El usuario ya existe"
+ "message": "Formato de correo electrónico no válido o credenciales (nombre de usuario o correo electrónico) ya en uso"
},
"unknown": {
"type": "Error Desconocido",
@@ -52,7 +52,7 @@
},
"authorized": {
"type": "Error de Autorización: ",
- "message": "Contraseña incorrecta"
+ "message": "Correo electrónico o contraseña no válidos, verifique que sean correctos"
}
},
"rules": {
diff --git a/webapp/src/components/auth/AuthManager.js b/webapp/src/components/auth/AuthManager.js
index b94db36e..09699b00 100644
--- a/webapp/src/components/auth/AuthManager.js
+++ b/webapp/src/components/auth/AuthManager.js
@@ -5,6 +5,7 @@ export default class AuthManager {
static #instance = null;
#isLoggedIn = false;
#axiosInstance = null;
+
constructor() {
if (!AuthManager.#instance) {
@@ -46,7 +47,7 @@ export default class AuthManager {
throw requestAnswer;
}
} catch (error) {
- onError(error);
+ onError(error);
}
}
@@ -100,19 +101,7 @@ export default class AuthManager {
throw requestAnswer;
}
} catch (error) {
- let errorType;
- switch (error.response ? error.response.status : null) {
- case 400:
- errorType = { type: "error.validation.type", message: "error.validation.message"};
- break;
- case 409:
- errorType = { type: "error.conflict.type", message: "error.conflict.message"};
- break;
- default:
- errorType = { type: "error.unknown.type", message: "error.unknown.message"};
- break;
- }
- onError(errorType);
+ onError(error);
}
}
}
diff --git a/webapp/src/pages/Login.jsx b/webapp/src/pages/Login.jsx
index 96d47505..8e2b9a86 100644
--- a/webapp/src/pages/Login.jsx
+++ b/webapp/src/pages/Login.jsx
@@ -46,9 +46,24 @@ export default function Login() {
"password": password
};
try {
- await new AuthManager().login(loginData, navigateToDashboard, setErrorMessage);
+ await new AuthManager().login(loginData, navigateToDashboard, setLocalizedErrorMessage);
} catch {
- setErrorMessage("Error desconocido");
+ const message = { type: t("error.login"), message: t("error.login-desc")};
+ setErrorMessage(message);
+ }
+ }
+
+ const setLocalizedErrorMessage = (error) => {
+ switch (error.response.status) {
+ case 400:
+ setErrorMessage({ type: t("error.validation.type"), message: t("error.validation.message")});
+ break;
+ case 401:
+ setErrorMessage({ type: t("error.authorized.type"), message: t("error.authorized.message")});
+ break;
+ default:
+ setErrorMessage({ type: t("error.login"), message: t("error.login-desc")});
+ break;
}
}
diff --git a/webapp/src/pages/Signup.jsx b/webapp/src/pages/Signup.jsx
index 192a4f61..9708f9bc 100644
--- a/webapp/src/pages/Signup.jsx
+++ b/webapp/src/pages/Signup.jsx
@@ -41,20 +41,21 @@ export default function Signup() {
try {
await new AuthManager().register(registerData, navigateToDashboard, setLocalizedErrorMessage);
} catch {
- setErrorMessage("Error desconocido");
+ const message = { type: t("error.register"), message: t("error.register-desc")};
+ setErrorMessage(message);
}
};
const setLocalizedErrorMessage = (error) => {
- switch (error.response ? error.response.status : null) {
+ switch (error.response.status) {
case 400:
- setErrorMessage({ type: t("error.validation.type"), message: t("error.validation.message")});
+ setErrorMessage({ type: t("error.conflict.type"), message: t("error.conflict.message")});
break;
case 401:
setErrorMessage({ type: t("error.authorized.type"), message: t("error.authorized.message")});
break;
default:
- setErrorMessage({ type: t("error.unknown.type"), message: t("error.unknown.message")});
+ setErrorMessage({ type: t("error.register"), message: t("error.register-desc")});
break;
}
}
From 0a6b8b2e30096045a25e68d4df46cdb3892d3b09 Mon Sep 17 00:00:00 2001
From: sergiorodriguezgarcia
<113514397+sergiorodriguezgarcia@users.noreply.github.com>
Date: Mon, 22 Apr 2024 18:09:09 +0200
Subject: [PATCH 2/9] feat: Adding colorMode button and first configuration
---
webapp/src/components/menu/LateralMenu.jsx | 8 ++++++--
webapp/src/index.js | 3 ++-
webapp/src/styles/theme.js | 2 ++
3 files changed, 10 insertions(+), 3 deletions(-)
diff --git a/webapp/src/components/menu/LateralMenu.jsx b/webapp/src/components/menu/LateralMenu.jsx
index f51a592b..833b3694 100644
--- a/webapp/src/components/menu/LateralMenu.jsx
+++ b/webapp/src/components/menu/LateralMenu.jsx
@@ -2,9 +2,9 @@ import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import { useNavigate } from "react-router-dom";
import { useTranslation } from "react-i18next";
-import { Box, Drawer, DrawerOverlay, DrawerContent, DrawerCloseButton, DrawerHeader, DrawerBody, DrawerFooter, Select, Button, Text, IconButton, Flex, Image } from '@chakra-ui/react';
+import { Box, Drawer, DrawerOverlay, DrawerContent, DrawerCloseButton, DrawerHeader, DrawerBody, DrawerFooter, Select, Button, Text, IconButton, Flex, Image, useColorMode } from '@chakra-ui/react';
import { FaChartBar, FaBook, FaTachometerAlt } from 'react-icons/fa';
-import { InfoIcon, SettingsIcon } from '@chakra-ui/icons';
+import { InfoIcon, SettingsIcon, SunIcon, MoonIcon } from '@chakra-ui/icons';
import AuthManager from "components/auth/AuthManager";
@@ -13,6 +13,7 @@ const LateralMenu = ({ isOpen, onClose, changeLanguage, isDashboard }) => {
const [selectedLanguage, setSelectedLanguage] = useState('');
const [isLoggedIn, setIsLoggedIn] = useState(false);
const { t, i18n } = useTranslation();
+ const { colorMode, toggleColorMode } = useColorMode();
useEffect(() => {
checkIsLoggedIn();
@@ -55,6 +56,9 @@ const LateralMenu = ({ isOpen, onClose, changeLanguage, isDashboard }) => {
KIWIQ
+ {
+ colorMode === "light" ? } aria-label={'DarkMode'} /> : } aria-label={'LightMode'} />
+ }
diff --git a/webapp/src/index.js b/webapp/src/index.js
index 0b32c148..6ad4954f 100644
--- a/webapp/src/index.js
+++ b/webapp/src/index.js
@@ -4,7 +4,7 @@ import './index.css';
import reportWebVitals from './reportWebVitals';
import {createBrowserRouter, RouterProvider} from 'react-router-dom';
import router from 'components/Router';
-import { ChakraProvider } from '@chakra-ui/react';
+import { ChakraProvider, ColorModeScript } from '@chakra-ui/react';
import "./i18n";
import theme from "./styles/theme";
@@ -13,6 +13,7 @@ const browserRouter = createBrowserRouter(router);
root.render(
+
);
diff --git a/webapp/src/styles/theme.js b/webapp/src/styles/theme.js
index c7126e63..85e00e36 100644
--- a/webapp/src/styles/theme.js
+++ b/webapp/src/styles/theme.js
@@ -117,5 +117,7 @@ const theme = extendTheme({
}
},
},
+ initialColorMode: 'system',
+ useSystemColorMode: true,
});
export default theme;
\ No newline at end of file
From a502223348610367cc5e7353c2c98a206d7c2a07 Mon Sep 17 00:00:00 2001
From: Sergio Rodriguez
Date: Tue, 23 Apr 2024 11:14:59 +0200
Subject: [PATCH 3/9] feat: Deleting the first config of colorMode
---
webapp/src/components/menu/LateralMenu.jsx | 8 ++------
webapp/src/index.js | 3 +--
webapp/src/styles/theme.js | 2 --
3 files changed, 3 insertions(+), 10 deletions(-)
diff --git a/webapp/src/components/menu/LateralMenu.jsx b/webapp/src/components/menu/LateralMenu.jsx
index 833b3694..f51a592b 100644
--- a/webapp/src/components/menu/LateralMenu.jsx
+++ b/webapp/src/components/menu/LateralMenu.jsx
@@ -2,9 +2,9 @@ import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import { useNavigate } from "react-router-dom";
import { useTranslation } from "react-i18next";
-import { Box, Drawer, DrawerOverlay, DrawerContent, DrawerCloseButton, DrawerHeader, DrawerBody, DrawerFooter, Select, Button, Text, IconButton, Flex, Image, useColorMode } from '@chakra-ui/react';
+import { Box, Drawer, DrawerOverlay, DrawerContent, DrawerCloseButton, DrawerHeader, DrawerBody, DrawerFooter, Select, Button, Text, IconButton, Flex, Image } from '@chakra-ui/react';
import { FaChartBar, FaBook, FaTachometerAlt } from 'react-icons/fa';
-import { InfoIcon, SettingsIcon, SunIcon, MoonIcon } from '@chakra-ui/icons';
+import { InfoIcon, SettingsIcon } from '@chakra-ui/icons';
import AuthManager from "components/auth/AuthManager";
@@ -13,7 +13,6 @@ const LateralMenu = ({ isOpen, onClose, changeLanguage, isDashboard }) => {
const [selectedLanguage, setSelectedLanguage] = useState('');
const [isLoggedIn, setIsLoggedIn] = useState(false);
const { t, i18n } = useTranslation();
- const { colorMode, toggleColorMode } = useColorMode();
useEffect(() => {
checkIsLoggedIn();
@@ -56,9 +55,6 @@ const LateralMenu = ({ isOpen, onClose, changeLanguage, isDashboard }) => {
KIWIQ
- {
- colorMode === "light" ? } aria-label={'DarkMode'} /> : } aria-label={'LightMode'} />
- }
diff --git a/webapp/src/index.js b/webapp/src/index.js
index 6ad4954f..0b32c148 100644
--- a/webapp/src/index.js
+++ b/webapp/src/index.js
@@ -4,7 +4,7 @@ import './index.css';
import reportWebVitals from './reportWebVitals';
import {createBrowserRouter, RouterProvider} from 'react-router-dom';
import router from 'components/Router';
-import { ChakraProvider, ColorModeScript } from '@chakra-ui/react';
+import { ChakraProvider } from '@chakra-ui/react';
import "./i18n";
import theme from "./styles/theme";
@@ -13,7 +13,6 @@ const browserRouter = createBrowserRouter(router);
root.render(
-
);
diff --git a/webapp/src/styles/theme.js b/webapp/src/styles/theme.js
index 85e00e36..c7126e63 100644
--- a/webapp/src/styles/theme.js
+++ b/webapp/src/styles/theme.js
@@ -117,7 +117,5 @@ const theme = extendTheme({
}
},
},
- initialColorMode: 'system',
- useSystemColorMode: true,
});
export default theme;
\ No newline at end of file
From dbe67ff50b1af51d4fa3701badf7a920320ef70d Mon Sep 17 00:00:00 2001
From: sergiorodriguezgarcia
<113514397+sergiorodriguezgarcia@users.noreply.github.com>
Date: Tue, 23 Apr 2024 16:16:27 +0200
Subject: [PATCH 4/9] feat: Adding more tests for the Login page
---
webapp/src/tests/Login.test.js | 31 +++++++++++++++++++++++++++++--
1 file changed, 29 insertions(+), 2 deletions(-)
diff --git a/webapp/src/tests/Login.test.js b/webapp/src/tests/Login.test.js
index 84459963..abf22641 100644
--- a/webapp/src/tests/Login.test.js
+++ b/webapp/src/tests/Login.test.js
@@ -1,5 +1,5 @@
import React from 'react';
-import { render, fireEvent, waitFor, act } from '@testing-library/react';
+import { render, fireEvent, waitFor } from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect';
import { MemoryRouter } from 'react-router';
import Login from '../pages/Login';
@@ -8,7 +8,6 @@ import MockAdapter from 'axios-mock-adapter';
import { HttpStatusCode } from 'axios';
import { ChakraProvider } from '@chakra-ui/react';
import theme from '../styles/theme';
-import Signup from 'pages/Signup';
jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
@@ -87,4 +86,32 @@ describe('Login Component', () => {
expect(getByTestId('error-message')).toBeInTheDocument();
});
});
+
+ it('renders button "GoBack"', () => {
+ const { getByTestId } = render(
+
+
+
+
+
+ );
+ const goBackButton = getByTestId('GoBack');
+ expect(goBackButton).toBeInTheDocument();
+ });
+
+ it('displays error message on failed format login attempt', async () => {
+ mockAxios.onPost().replyOnce(HttpStatusCode.BadRequest);
+ const { getByPlaceholderText, getByTestId } = render();
+ const emailInput = getByPlaceholderText('session.email');
+ const passwordInput = getByPlaceholderText('session.password');
+ const loginButton = getByTestId('Login');
+
+ fireEvent.change(emailInput, { target: { value: 'test' } });
+ fireEvent.change(passwordInput, { target: { value: 'test' } });
+ fireEvent.click(loginButton);
+
+ await waitFor(() => {
+ expect(getByTestId('error-message')).toBeInTheDocument();
+ });
+ });
});
\ No newline at end of file
From 42c7cb335e47cfeb873e345b9f108235a0127ec4 Mon Sep 17 00:00:00 2001
From: sergiorodriguezgarcia
<113514397+sergiorodriguezgarcia@users.noreply.github.com>
Date: Tue, 23 Apr 2024 16:20:59 +0200
Subject: [PATCH 5/9] feat: Adding more tests for the Signup page
---
webapp/src/tests/Signup.test.js | 43 ++++++++++++++++++++++++++++++++-
1 file changed, 42 insertions(+), 1 deletion(-)
diff --git a/webapp/src/tests/Signup.test.js b/webapp/src/tests/Signup.test.js
index 9b2112b6..550fe67a 100644
--- a/webapp/src/tests/Signup.test.js
+++ b/webapp/src/tests/Signup.test.js
@@ -4,9 +4,14 @@ import { MemoryRouter } from 'react-router';
import Signup from '../pages/Signup';
import { ChakraProvider } from '@chakra-ui/react';
import theme from '../styles/theme';
-import MockAdapter from 'axios-mock-adapter';
import AuthManager from 'components/auth/AuthManager';
import { HttpStatusCode } from 'axios';
+import MockAdapter from 'axios-mock-adapter';
+
+jest.mock('react-router-dom', () => ({
+ ...jest.requireActual('react-router-dom'),
+ useNavigate: jest.fn(),
+}));
jest.mock('react-i18next', () => ({
useTranslation: () => {
@@ -19,8 +24,17 @@ jest.mock('react-i18next', () => ({
},
}));
+const authManager = new AuthManager();
+let mockAxios = new MockAdapter(authManager.getAxiosInstance());
+
describe('Signup Component', () => {
+ beforeEach(() => {
+ authManager.reset();
+ jest.clearAllMocks();
+ mockAxios = new MockAdapter(authManager.getAxiosInstance());
+ });
+
it('renders form elements correctly', () => {
const { getByPlaceholderText } = render();
@@ -74,4 +88,31 @@ describe('Signup Component', () => {
expect(confirmPasswordInput.value).toBe('newPassword');
});
+ it('displays error message on failed login attempt', async () => {
+ mockAxios.onPost().replyOnce(HttpStatusCode.BadRequest);
+ const { getByPlaceholderText, getByTestId } = render();
+ const emailInput = getByPlaceholderText('session.email');
+ const passwordInput = getByPlaceholderText('session.password');
+ const loginButton = getByTestId('Sign up');
+
+ fireEvent.change(emailInput, { target: { value: 'test@example.com' } });
+ fireEvent.change(passwordInput, { target: { value: 'password123' } });
+ fireEvent.click(loginButton);
+
+ await waitFor(() => {
+ expect(getByTestId('error-message')).toBeInTheDocument();
+ });
+ });
+
+ it('renders button "GoBack"', () => {
+ const { getByTestId } = render(
+
+
+
+
+
+ );
+ const goBackButton = getByTestId('GoBack');
+ expect(goBackButton).toBeInTheDocument();
+ });
});
\ No newline at end of file
From ca2557be3561d86c8788f30ae4aa8bb91a925bbb Mon Sep 17 00:00:00 2001
From: sergiorodriguezgarcia
<113514397+sergiorodriguezgarcia@users.noreply.github.com>
Date: Tue, 23 Apr 2024 16:37:24 +0200
Subject: [PATCH 6/9] feat: Adding more tests to complete the coverage
---
webapp/src/tests/Login.test.js | 16 ++++++++++++++++
webapp/src/tests/Signup.test.js | 30 +++++++++++++++++-------------
2 files changed, 33 insertions(+), 13 deletions(-)
diff --git a/webapp/src/tests/Login.test.js b/webapp/src/tests/Login.test.js
index abf22641..a2fa1a88 100644
--- a/webapp/src/tests/Login.test.js
+++ b/webapp/src/tests/Login.test.js
@@ -114,4 +114,20 @@ describe('Login Component', () => {
expect(getByTestId('error-message')).toBeInTheDocument();
});
});
+
+ it('displays error message on unauthorized login attempt', async () => {
+ mockAxios.onPost().replyOnce(HttpStatusCode.Unauthorized);
+ const { getByPlaceholderText, getByTestId } = render();
+ const emailInput = getByPlaceholderText('session.email');
+ const passwordInput = getByPlaceholderText('session.password');
+ const loginButton = getByTestId('Login');
+
+ fireEvent.change(emailInput, { target: { value: 'test@example.com' } });
+ fireEvent.change(passwordInput, { target: { value: 'test' } });
+ fireEvent.click(loginButton);
+
+ await waitFor(() => {
+ expect(getByTestId('error-message')).toBeInTheDocument();
+ });
+ });
});
\ No newline at end of file
diff --git a/webapp/src/tests/Signup.test.js b/webapp/src/tests/Signup.test.js
index 550fe67a..18f91ecf 100644
--- a/webapp/src/tests/Signup.test.js
+++ b/webapp/src/tests/Signup.test.js
@@ -88,31 +88,35 @@ describe('Signup Component', () => {
expect(confirmPasswordInput.value).toBe('newPassword');
});
- it('displays error message on failed login attempt', async () => {
+ it('displays error message on failed register attempt', async () => {
mockAxios.onPost().replyOnce(HttpStatusCode.BadRequest);
const { getByPlaceholderText, getByTestId } = render();
const emailInput = getByPlaceholderText('session.email');
const passwordInput = getByPlaceholderText('session.password');
- const loginButton = getByTestId('Sign up');
+ const registerButton = getByTestId('Sign up');
fireEvent.change(emailInput, { target: { value: 'test@example.com' } });
fireEvent.change(passwordInput, { target: { value: 'password123' } });
- fireEvent.click(loginButton);
+ fireEvent.click(registerButton);
await waitFor(() => {
expect(getByTestId('error-message')).toBeInTheDocument();
});
});
- it('renders button "GoBack"', () => {
- const { getByTestId } = render(
-
-
-
-
-
- );
- const goBackButton = getByTestId('GoBack');
- expect(goBackButton).toBeInTheDocument();
+ it('displays error message on unauthorized register attempt', async () => {
+ mockAxios.onPost().replyOnce(HttpStatusCode.Unauthorized);
+ const { getByPlaceholderText, getByTestId } = render();
+ const emailInput = getByPlaceholderText('session.email');
+ const passwordInput = getByPlaceholderText('session.password');
+ const registerButton = getByTestId('Sign up');
+
+ fireEvent.change(emailInput, { target: { value: 'test@example.com' } });
+ fireEvent.change(passwordInput, { target: { value: 'test' } });
+ fireEvent.click(registerButton);
+
+ await waitFor(() => {
+ expect(getByTestId('error-message')).toBeInTheDocument();
+ });
});
});
\ No newline at end of file
From 296161e9c5133134a176636c8db161fe9c6a2b89 Mon Sep 17 00:00:00 2001
From: Diego Villanueva
Date: Wed, 24 Apr 2024 00:46:09 +0200
Subject: [PATCH 7/9] Chore: Added country flag questions
---
.../questions/answer/AnswerCategory.java | 2 +-
questiongenerator/src/main/java/Main.java | 5 ++
.../src/main/java/model/AnswerCategory.java | 2 +-
.../java/templates/CountryFlagQuestion.java | 77 +++++++++++++++++++
4 files changed, 84 insertions(+), 2 deletions(-)
create mode 100644 questiongenerator/src/main/java/templates/CountryFlagQuestion.java
diff --git a/api/src/main/java/lab/en2b/quizapi/questions/answer/AnswerCategory.java b/api/src/main/java/lab/en2b/quizapi/questions/answer/AnswerCategory.java
index cc8239b0..f74bd1b0 100644
--- a/api/src/main/java/lab/en2b/quizapi/questions/answer/AnswerCategory.java
+++ b/api/src/main/java/lab/en2b/quizapi/questions/answer/AnswerCategory.java
@@ -1,6 +1,6 @@
package lab.en2b.quizapi.questions.answer;
public enum AnswerCategory {
- CAPITAL_CITY, COUNTRY, SONG, STADIUM, BALLON_DOR, GAMES_PUBLISHER, PAINTING, WTPOKEMON, GAMES_COUNTRY, GAMES_GENRE, BASKETBALL_VENUE
+ CAPITAL_CITY, COUNTRY, SONG, STADIUM, BALLON_DOR, GAMES_PUBLISHER, PAINTING, WTPOKEMON, GAMES_COUNTRY, GAMES_GENRE, BASKETBALL_VENUE, COUNTRY_FLAG
}
diff --git a/questiongenerator/src/main/java/Main.java b/questiongenerator/src/main/java/Main.java
index e9f76e02..3b7a256b 100644
--- a/questiongenerator/src/main/java/Main.java
+++ b/questiongenerator/src/main/java/Main.java
@@ -55,6 +55,11 @@ public static void main(String[] args) {
new WhosThatPokemonQuestion("es");
}
+ if (GeneralRepositoryStorer.doesntExist(AnswerCategory.COUNTRY_FLAG)) {
+ new CountryFlagQuestion("en");
+ new CountryFlagQuestion("es");
+ }
+
/*
// VIDEOS not yet supported
if(GeneralRepositoryStorer.doesntExist(AnswerCategory.SONG.toString())) {
diff --git a/questiongenerator/src/main/java/model/AnswerCategory.java b/questiongenerator/src/main/java/model/AnswerCategory.java
index 7de719bb..c4b0f5a1 100644
--- a/questiongenerator/src/main/java/model/AnswerCategory.java
+++ b/questiongenerator/src/main/java/model/AnswerCategory.java
@@ -1,6 +1,6 @@
package model;
public enum AnswerCategory {
- CAPITAL_CITY, COUNTRY, SONG, STADIUM, BALLON_DOR, GAMES_PUBLISHER, PAINTING, WTPOKEMON, GAMES_COUNTRY, GAMES_GENRE, BASKETBALL_VENUE
+ CAPITAL_CITY, COUNTRY, SONG, STADIUM, BALLON_DOR, GAMES_PUBLISHER, PAINTING, WTPOKEMON, GAMES_COUNTRY, GAMES_GENRE, BASKETBALL_VENUE, COUNTRY_FLAG
}
diff --git a/questiongenerator/src/main/java/templates/CountryFlagQuestion.java b/questiongenerator/src/main/java/templates/CountryFlagQuestion.java
new file mode 100644
index 00000000..3b254737
--- /dev/null
+++ b/questiongenerator/src/main/java/templates/CountryFlagQuestion.java
@@ -0,0 +1,77 @@
+package templates;
+
+import model.QuestionCategory;
+import model.QuestionType;
+import model.Answer;
+import model.AnswerCategory;
+import model.Question;
+import org.json.JSONObject;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class CountryFlagQuestion extends QuestionTemplate {
+
+ private static final String[] spanishStringsIni = {"¿Que país tiene esta bandera? ", "¿A qué país pertenece esta bandera? ", "¿De qué país es esta bandera? ", "¿Cuál es el país de esta bandera? "};
+ private static final String[] englishStringsIni= {"Which country has this flag? ", "To which country belongs this flag? ", "From which country is this flag? ", "What is the country represented by this flag? "};
+
+ List countryLabels;
+
+ public CountryFlagQuestion(String langCode) {
+ super(langCode);
+ }
+
+ @Override
+ public void setQuery() {
+ this.sparqlQuery = "SELECT ?countryLabel ?flagLabel\n" +
+ "WHERE " +
+ "{ " +
+ " ?country wdt:P31 wd:Q6256; " +
+ " wdt:P41 ?flag. " +
+ " SERVICE wikibase:label { bd:serviceParam wikibase:language \"" + langCode + "\". } " +
+ "}";
+ }
+
+ @Override
+ public void processResults() {
+ countryLabels = new ArrayList<>();
+ List questions = new ArrayList<>();
+ List answers = new ArrayList<>();
+
+ for (int i = 0; i < results.length(); i++) {
+ JSONObject result = results.getJSONObject(i);
+ String countryLabel = result.getJSONObject("countryLabel").getString("value");
+ String flagLabel = result.getJSONObject("flagLabel").getString("value");
+
+ if (needToSkip(countryLabel, flagLabel)) {
+ continue;
+ }
+
+ Answer a = new Answer(countryLabel, AnswerCategory.COUNTRY_FLAG, langCode);
+ answers.add(a);
+
+ if (langCode.equals("es")){
+ String questionString = spanishStringsIni[i%4] + QGHelper.LINKCONCAT + flagLabel;
+ questions.add(new Question(a, questionString, QuestionCategory.GEOGRAPHY, QuestionType.IMAGE));
+ } else {
+ String questionString = englishStringsIni[i%4] + QGHelper.LINKCONCAT + flagLabel;
+ questions.add(new Question(a, questionString, QuestionCategory.GEOGRAPHY, QuestionType.IMAGE));
+ }
+ }
+ repository.saveAll(new ArrayList<>(answers));
+ repository.saveAll(new ArrayList<>(questions));
+ }
+
+ private boolean needToSkip(String countryLabel, String venueLabel){
+ if (countryLabels.contains(countryLabel)) {
+ return true;
+ }
+ countryLabels.add(countryLabel);
+
+ if (QGHelper.isEntityName(countryLabel) || QGHelper.isEntityName(venueLabel)) {
+ return true;
+ }
+
+ return false;
+ }
+}
From 60c5883942a40c1e9aec10cdbeff510215d83984 Mon Sep 17 00:00:00 2001
From: jjgancfer
Date: Wed, 24 Apr 2024 20:11:39 +0200
Subject: [PATCH 8/9] test: add more tests to the game
---
webapp/src/pages/Game.jsx | 3 +-
webapp/src/tests/Game.test.js | 548 +++++++++++++++++++---------------
2 files changed, 308 insertions(+), 243 deletions(-)
diff --git a/webapp/src/pages/Game.jsx b/webapp/src/pages/Game.jsx
index 6b3b2dd0..fec21d9e 100644
--- a/webapp/src/pages/Game.jsx
+++ b/webapp/src/pages/Game.jsx
@@ -150,7 +150,6 @@ export default function Game() {
navigate("/dashboard");
}
} catch (error) {
- console.error("Error initializing game:", error);
navigate("/dashboard");
}
};
@@ -198,7 +197,7 @@ export default function Game() {
{
(!loading && hasImage) &&
-
+
}
diff --git a/webapp/src/tests/Game.test.js b/webapp/src/tests/Game.test.js
index 14255d74..4b0ef25f 100644
--- a/webapp/src/tests/Game.test.js
+++ b/webapp/src/tests/Game.test.js
@@ -1,241 +1,307 @@
-import React from 'react';
-import {render, fireEvent, screen, act, waitFor} from '@testing-library/react';
-import { MemoryRouter } from 'react-router';
-import Game from '../pages/Game';
-import { ChakraProvider } from '@chakra-ui/react';
-import theme from '../styles/theme';
-import AuthManager from "../components/auth/AuthManager";
-import MockAdapter from "axios-mock-adapter";
-import {HttpStatusCode} from "axios";
-
-jest.mock('react-i18next', () => ({
- useTranslation: () => {
- return {
- t: (str) => str,
- i18n: {
- changeLanguage: () => new Promise(() => {}),
- language: "en"
- },
- }
- },
-}));
-
-const authManager = new AuthManager();
-let mockAxios = new MockAdapter(authManager.getAxiosInstance());
-const api = process.env.REACT_APP_API_ENDPOINT;
-
-const game = {
- "id": 23483743,
- "user": {
- "id": 1,
- "username": "Hordi Jurtado",
- "email": "chipiChipi@chapaChapa.es "
- },
- "rounds": 9,
- "gamemode": "KIWI_QUEST",
- "gameOver": false,
- "actual_round": 0,
- "correctly_answered_questions": 0,
- "round_start_time": new Date(),
- "round_duration": 20
-};
-const round = {
- "id": 23483743,
- "user": {
- "id": 1,
- "username": "Hordi Jurtado",
- "email": "chipiChipi@chapaChapa.es "
- },
- "rounds": 9,
- "gamemode": "KIWI_QUEST",
- "gameOver": false,
- "actual_round": 1,
- "correctly_answered_questions": 0,
- "round_start_time": new Date(),
- "round_duration": 20
-};
-const question = {
- "id": 1,
- "content": "What is the capital of France?",
- "answers": [
- {
- "id": 1,
- "text": "Paris",
- "category": "CITY"
- },
- {
- "id": 2,
- "text": "London",
- "category": "CITY"
- },
- {
- "id": 3,
- "text": "Berlin",
- "category": "CITY"
- },
- {
- "id": 4,
- "text": "Madrid",
- "category": "CITY"
- }
- ],
- "questionCategory": "GEOGRAPHY",
- "answerCategory": "CITY",
- "language": "en",
- "type": "MULTIPLE_CHOICE",
- "image": "https://www.example.com/image.jpg"
-}
-
-describe('Game component', () => {
-
- describe("there is no prior game", () => {
-
- beforeEach(() => {
- mockAxios.onGet(`${api}/games/play`).reply(HttpStatusCode.Ok, game);
- mockAxios.onGet(`${api}/games/${game.id}/question`).replyOnce(HttpStatusCode.Conflict);
- mockAxios.onPost(`${api}/games/${game.id}/startRound`).reply(HttpStatusCode.Ok, round);
- mockAxios.onGet(`${api}/games/${game.id}/question`).reply(HttpStatusCode.Ok, question);
- });
-
- afterEach(() => {
- authManager.reset();
- mockAxios = new MockAdapter(authManager.getAxiosInstance());
- });
-
- it("renders correctly", async () => {
- const { container } = render();
-
- await waitFor(() => {
- expect(container.querySelectorAll(".question-answer").length).toBe(4);
- expect(container.querySelector("#question").textContent).toBe(question.content);
- expect(mockAxios.history.get.length).toBe(3);
- expect(mockAxios.history.post.length).toBe(1);
- });
- });
-
- test('disables next button when no option is selected', async () => {
- render();
- const nextButton = await screen.findByTestId('Next');
-
- expect(nextButton).toBeDisabled();
- });
-
- test('enables next button when an option is selected', async () => {
- render();
- const option1Button = await screen.findByTestId('Option1');
- const nextButton = await screen.findByTestId('Next');
-
- await act(() => fireEvent.click(option1Button));
-
- expect(nextButton).toBeEnabled();
- });
-
- test("only the last selection is chosen", async () => {
- mockAxios.onPost(`${api}/games/${game.id}/answer`).replyOnce(HttpStatusCode.Ok, {
- was_correct: true
- });
-
- render();
- await screen.findByTestId('Option1');
-
- await act(() => fireEvent.click(screen.getByTestId('Option1')));
- await act(() => fireEvent.click(screen.getByTestId('Option3')));
- await act(() => fireEvent.click(screen.getByTestId('Next')));
-
- await waitFor(() => {
- expect(mockAxios.history.post[1].data).toBe(JSON.stringify({"answer_id": question.answers[2].id}));
- })
- });
-
- test("after answering with an answer that is true, confetti is shown", async () => {
- mockAxios.onPost(`${api}/games/${game.id}/answer`).replyOnce(HttpStatusCode.Ok, {
- was_correct: true
- });
-
- render();
- await screen.findByTestId('Option1');
-
- await act(() => fireEvent.click(screen.getByTestId('Option1')));
- await act(() => fireEvent.click(screen.getByTestId('Next')));
-
- await waitFor(() => {
- expect(screen.getByTestId("confetti")).toBeEnabled();
- });
- });
- });
-
- describe("there is a prior game", () => {
-
- beforeEach(() => {
- mockAxios.onGet(`${api}/games/play`).reply(HttpStatusCode.Ok, game);
- mockAxios.onGet(`${api}/games/${game.id}/question`).reply(HttpStatusCode.Ok, question);
- mockAxios.onPost(`${api}/games/${game.id}/startRound`).reply(HttpStatusCode.Ok, round);
- });
-
- afterEach(() => {
- authManager.reset();
- mockAxios = new MockAdapter(authManager.getAxiosInstance());
- });
-
- it("renders correctly", async () => {
- const { container } = render();
-
- await waitFor(() => {
- expect(container.querySelectorAll(".question-answer").length).toBe(4);
- expect(container.querySelector("#question").textContent).toBe(question.content);
- expect(mockAxios.history.get.length).toBe(2);
- expect(mockAxios.history.post.length).toBe(0);
- });
- });
-
- test('disables next button when no option is selected', async () => {
- render();
- const nextButton = await screen.findByTestId('Next');
-
- expect(nextButton).toBeDisabled();
- });
-
- test('enables next button when an option is selected', async () => {
- render();
- const option1Button = await screen.findByTestId('Option1');
- const nextButton = await screen.findByTestId('Next');
-
- await act(() => fireEvent.click(option1Button));
-
- expect(nextButton).toBeEnabled();
- });
-
- test("only the last selection is chosen", async () => {
- mockAxios.onPost(`${api}/games/${game.id}/answer`).replyOnce(HttpStatusCode.Ok, {
- was_correct: true
- });
-
- render();
- await screen.findByTestId('Option1');
-
- await act(() => fireEvent.click(screen.getByTestId('Option1')));
- await act(() => fireEvent.click(screen.getByTestId('Option3')));
- await act(() => fireEvent.click(screen.getByTestId('Next')));
-
- await waitFor(() => {
- expect(mockAxios.history.post[0].data).toBe(JSON.stringify({"answer_id": question.answers[2].id}));
- })
- });
-
- test("there is no confetti if the answer is wrong", async () => {
- mockAxios.onPost(`${api}/games/${game.id}/answer`).replyOnce(HttpStatusCode.Ok, {
- was_correct: false
- });
-
- const {container} = render();
- await screen.findByTestId('Option1');
-
- await act(() => fireEvent.click(screen.getByTestId('Option1')));
- await act(() => fireEvent.click(screen.getByTestId('Next')));
-
- await waitFor(() => {
- expect(container.querySelectorAll("[data-testid='confetti']").length).toBe(0);
- });
- });
- });
-});
+import React from 'react';
+import {render, fireEvent, screen, act, waitFor} from '@testing-library/react';
+import { MemoryRouter } from 'react-router';
+import Game from '../pages/Game';
+import { ChakraProvider } from '@chakra-ui/react';
+import theme from '../styles/theme';
+import AuthManager from "../components/auth/AuthManager";
+import MockAdapter from "axios-mock-adapter";
+import {HttpStatusCode} from "axios";
+
+jest.mock('react-i18next', () => ({
+ useTranslation: () => {
+ return {
+ t: (str) => str,
+ i18n: {
+ changeLanguage: () => new Promise(() => {}),
+ language: "en"
+ },
+ }
+ },
+}));
+
+const authManager = new AuthManager();
+let mockAxios = new MockAdapter(authManager.getAxiosInstance());
+const api = process.env.REACT_APP_API_ENDPOINT;
+
+const game = {
+ "id": 23483743,
+ "user": {
+ "id": 1,
+ "username": "Hordi Jurtado",
+ "email": "chipiChipi@chapaChapa.es "
+ },
+ "rounds": 9,
+ "gamemode": "KIWI_QUEST",
+ "gameOver": false,
+ "actual_round": 0,
+ "correctly_answered_questions": 0,
+ "round_start_time": new Date(),
+ "round_duration": 20
+};
+const round = {
+ "id": 23483743,
+ "user": {
+ "id": 1,
+ "username": "Hordi Jurtado",
+ "email": "chipiChipi@chapaChapa.es "
+ },
+ "rounds": 9,
+ "gamemode": "KIWI_QUEST",
+ "gameOver": false,
+ "actual_round": 1,
+ "correctly_answered_questions": 0,
+ "round_start_time": new Date(),
+ "round_duration": 20
+};
+const question = {
+ "id": 1,
+ "content": "What is the capital of France?",
+ "answers": [
+ {
+ "id": 1,
+ "text": "Paris",
+ "category": "CITY"
+ },
+ {
+ "id": 2,
+ "text": "London",
+ "category": "CITY"
+ },
+ {
+ "id": 3,
+ "text": "Berlin",
+ "category": "CITY"
+ },
+ {
+ "id": 4,
+ "text": "Madrid",
+ "category": "CITY"
+ }
+ ],
+ "questionCategory": "GEOGRAPHY",
+ "answerCategory": "CITY",
+ "language": "en",
+ "type": "MULTIPLE_CHOICE",
+ "image": "https://www.example.com/image.jpg"
+}
+
+const mockFunction = jest.fn();
+jest.mock('react-router-dom', () => ({
+ ...jest.requireActual('react-router-dom'),
+ useNavigate: () => mockFunction,
+}));
+
+
+
+describe('Game component', () => {
+
+ describe("there is no prior game", () => {
+
+ beforeEach(() => {
+ mockAxios.onGet(`${api}/games/play`).reply(HttpStatusCode.Ok, game);
+ mockAxios.onGet(`${api}/games/${game.id}/question`).replyOnce(HttpStatusCode.Conflict);
+ mockAxios.onPost(`${api}/games/${game.id}/startRound`).reply(HttpStatusCode.Ok, round);
+ mockAxios.onGet(`${api}/games/${game.id}/question`).reply(HttpStatusCode.Ok, question);
+ });
+
+ afterEach(() => {
+ authManager.reset();
+ mockAxios = new MockAdapter(authManager.getAxiosInstance());
+ });
+
+ it("renders correctly", async () => {
+ const { container } = render();
+
+ await waitFor(() => {
+ expect(container.querySelectorAll(".question-answer").length).toBe(4);
+ expect(container.querySelector("#question").textContent).toBe(question.content);
+ expect(mockAxios.history.get.length).toBe(3);
+ expect(mockAxios.history.post.length).toBe(1);
+ });
+ });
+
+ test('disables next button when no option is selected', async () => {
+ render();
+ const nextButton = await screen.findByTestId('Next');
+
+ expect(nextButton).toBeDisabled();
+ });
+
+ test('enables next button when an option is selected', async () => {
+ render();
+ const option1Button = await screen.findByTestId('Option1');
+ const nextButton = await screen.findByTestId('Next');
+
+ await act(() => fireEvent.click(option1Button));
+
+ expect(nextButton).toBeEnabled();
+ });
+
+ test("only the last selection is chosen", async () => {
+ mockAxios.onPost(`${api}/games/${game.id}/answer`).replyOnce(HttpStatusCode.Ok, {
+ was_correct: true
+ });
+
+ render();
+ await screen.findByTestId('Option1');
+
+ await act(() => fireEvent.click(screen.getByTestId('Option1')));
+ await act(() => fireEvent.click(screen.getByTestId('Option3')));
+ await act(() => fireEvent.click(screen.getByTestId('Next')));
+
+ await waitFor(() => {
+ expect(mockAxios.history.post[1].data).toBe(JSON.stringify({"answer_id": question.answers[2].id}));
+ })
+ });
+
+ test("after answering with an answer that is true, confetti is shown", async () => {
+ mockAxios.onPost(`${api}/games/${game.id}/answer`).replyOnce(HttpStatusCode.Ok, {
+ was_correct: true
+ });
+
+ render();
+ await screen.findByTestId('Option1');
+
+ await act(() => fireEvent.click(screen.getByTestId('Option1')));
+ await act(() => fireEvent.click(screen.getByTestId('Next')));
+
+ await waitFor(() => {
+ expect(screen.getByTestId("confetti")).toBeEnabled();
+ });
+ });
+
+ describe("it tries to navigate to dashboard after", () => {
+
+ beforeEach(() => {
+ mockAxios.reset();
+ });
+
+ test("a not valid object is returned when trying to create a game", async () => {
+ mockAxios.onGet(`${api}/games/play`).reply(HttpStatusCode.Ok, undefined);
+ render();
+
+ await waitFor(() => {
+ expect(mockFunction).toHaveBeenCalled();
+ expect(mockFunction).toHaveBeenCalledWith("/dashboard");
+ });
+ });
+
+ test("an object is returned when trying to create a game", async () => {
+ mockAxios.onGet(`${api}/games/play`).reply(HttpStatusCode.Ok, {});
+ render();
+
+ await waitFor(() => {
+ expect(mockFunction).toHaveBeenCalled();
+ expect(mockFunction).toHaveBeenCalledWith("/dashboard");
+ });
+ });
+
+ test("the petition to get the game fails", async () => {
+ mockAxios.onGet(`${api}/games/play`).reply(HttpStatusCode.InternalServerError);
+ render();
+
+ await waitFor(() => {
+ expect(mockFunction).toHaveBeenCalled();
+ expect(mockFunction).toHaveBeenCalledWith("/dashboard");
+ });
+ })
+ })
+ });
+
+ describe("there is a prior game", () => {
+
+ beforeEach(() => {
+ mockAxios.onGet(`${api}/games/play`).reply(HttpStatusCode.Ok, game);
+ mockAxios.onGet(`${api}/games/${game.id}/question`).reply(HttpStatusCode.Ok, question);
+ mockAxios.onPost(`${api}/games/${game.id}/startRound`).reply(HttpStatusCode.Ok, round);
+ });
+
+ afterEach(() => {
+ authManager.reset();
+ mockAxios = new MockAdapter(authManager.getAxiosInstance());
+ });
+
+ it("renders correctly", async () => {
+ const { container } = render();
+
+ await waitFor(() => {
+ expect(container.querySelectorAll(".question-answer").length).toBe(4);
+ expect(container.querySelector("#question").textContent).toBe(question.content);
+ expect(mockAxios.history.get.length).toBe(2);
+ expect(mockAxios.history.post.length).toBe(0);
+ });
+ });
+
+ test('disables next button when no option is selected', async () => {
+ render();
+ const nextButton = await screen.findByTestId('Next');
+
+ expect(nextButton).toBeDisabled();
+ });
+
+ test('enables next button when an option is selected', async () => {
+ render();
+ const option1Button = await screen.findByTestId('Option1');
+ const nextButton = await screen.findByTestId('Next');
+
+ await act(() => fireEvent.click(option1Button));
+
+ expect(nextButton).toBeEnabled();
+ });
+
+ test("only the last selection is chosen", async () => {
+ mockAxios.onPost(`${api}/games/${game.id}/answer`).replyOnce(HttpStatusCode.Ok, {
+ was_correct: true
+ });
+
+ render();
+ await screen.findByTestId('Option1');
+
+ await act(() => fireEvent.click(screen.getByTestId('Option1')));
+ await act(() => fireEvent.click(screen.getByTestId('Option3')));
+ await act(() => fireEvent.click(screen.getByTestId('Next')));
+
+ await waitFor(() => {
+ expect(mockAxios.history.post[0].data).toBe(JSON.stringify({"answer_id": question.answers[2].id}));
+ })
+ });
+
+ test("there is no confetti if the answer is wrong", async () => {
+ mockAxios.onPost(`${api}/games/${game.id}/answer`).replyOnce(HttpStatusCode.Ok, {
+ was_correct: false
+ });
+
+ const {container} = render();
+ await screen.findByTestId('Option1');
+
+ await act(() => fireEvent.click(screen.getByTestId('Option1')));
+ await act(() => fireEvent.click(screen.getByTestId('Next')));
+
+ await waitFor(() => {
+ expect(container.querySelectorAll("[data-testid='confetti']").length).toBe(0);
+ });
+ });
+
+ test("there is no image shown if the URI is not passed", async () => {
+ mockAxios.reset();
+ let clone = {...question};
+ delete clone.image;
+ mockAxios.onGet(`${api}/games/play`).reply(HttpStatusCode.Ok, game);
+ mockAxios.onGet(`${api}/games/${game.id}/question`).reply(HttpStatusCode.Ok, clone);
+ mockAxios.onPost(`${api}/games/${game.id}/startRound`).reply(HttpStatusCode.Ok, round);
+
+ const {container} = render();
+ await waitFor(() => {
+ expect(container.querySelectorAll("[data-testid='image']").length).toBe(0);
+ });
+ });
+
+ test("there is image shown if it is passed the URI", async () => {
+ const {container} = render();
+ await waitFor(() => {
+ expect(container.querySelectorAll("[data-testid='image']").length).toBe(1);
+ });
+ });
+ });
+});
From 50f03afb275d5e5ef79e47edbbb74972fd17e883 Mon Sep 17 00:00:00 2001
From: jjgancfer
Date: Wed, 24 Apr 2024 20:46:11 +0200
Subject: [PATCH 9/9] test: DashboardButton tests
---
.../components/dashboard/DashboardButton.jsx | 122 +++++++++---------
webapp/src/pages/Game.jsx | 1 -
webapp/src/tests/DashboardButton.test.js | 25 ++++
3 files changed, 86 insertions(+), 62 deletions(-)
create mode 100644 webapp/src/tests/DashboardButton.test.js
diff --git a/webapp/src/components/dashboard/DashboardButton.jsx b/webapp/src/components/dashboard/DashboardButton.jsx
index c4e93af5..9323c09b 100644
--- a/webapp/src/components/dashboard/DashboardButton.jsx
+++ b/webapp/src/components/dashboard/DashboardButton.jsx
@@ -1,62 +1,62 @@
-import React from "react";
-import PropTypes from 'prop-types';
-import { Button, Box } from "@chakra-ui/react";
-import { FaKiwiBird, FaRandom, FaPalette } from "react-icons/fa";
-import { TbWorld } from "react-icons/tb";
-import { IoIosFootball, IoLogoGameControllerB } from "react-icons/io";
-
-const DashboardButton = ({ label, selectedButton, onClick, iconName }) => {
- const isSelected = label === selectedButton;
- let icon = null;
-
- switch (iconName) {
- case "FaKiwiBird":
- icon = ;
- break;
- case "IoIosFootball":
- icon = ;
- break;
- case "FaGlobeAmericas":
- icon = ;
- break;
- case "IoLogoGameControllerB":
- icon = ;
- break;
- case "FaPalette":
- icon = ;
- break;
- case "FaRandom":
- icon = ;
- break;
- default:
- break;
- }
-
- return (
-
- );
-};
-
-DashboardButton.propTypes = {
- label: PropTypes.string.isRequired,
- selectedButton: PropTypes.string.isRequired,
- onClick: PropTypes.func.isRequired,
- iconName: PropTypes.string.isRequired
-};
-
+import React from "react";
+import PropTypes from 'prop-types';
+import { Button, Box } from "@chakra-ui/react";
+import { FaKiwiBird, FaRandom, FaPalette } from "react-icons/fa";
+import { TbWorld } from "react-icons/tb";
+import { IoIosFootball, IoLogoGameControllerB } from "react-icons/io";
+
+const DashboardButton = ({ label, selectedButton, onClick, iconName }) => {
+ const isSelected = label === selectedButton;
+ let icon = null;
+
+ switch (iconName) {
+ case "FaKiwiBird":
+ icon = ;
+ break;
+ case "IoIosFootball":
+ icon = ;
+ break;
+ case "FaGlobeAmericas":
+ icon = ;
+ break;
+ case "IoLogoGameControllerB":
+ icon = ;
+ break;
+ case "FaPalette":
+ icon = ;
+ break;
+ case "FaRandom":
+ icon = ;
+ break;
+ default:
+ break;
+ }
+
+ return (
+
+ );
+};
+
+DashboardButton.propTypes = {
+ label: PropTypes.string.isRequired,
+ selectedButton: PropTypes.string.isRequired,
+ onClick: PropTypes.func.isRequired,
+ iconName: PropTypes.string.isRequired
+};
+
export default DashboardButton;
\ No newline at end of file
diff --git a/webapp/src/pages/Game.jsx b/webapp/src/pages/Game.jsx
index fec21d9e..029d884e 100644
--- a/webapp/src/pages/Game.jsx
+++ b/webapp/src/pages/Game.jsx
@@ -56,7 +56,6 @@ export default function Game() {
if (error.response.status === HttpStatusCode.Conflict) {
throw error;
} else {
- console.error("Error fetching question:", error);
navigate("/dashboard");
}
}
diff --git a/webapp/src/tests/DashboardButton.test.js b/webapp/src/tests/DashboardButton.test.js
new file mode 100644
index 00000000..6ad74319
--- /dev/null
+++ b/webapp/src/tests/DashboardButton.test.js
@@ -0,0 +1,25 @@
+import each from "jest-each";
+import {act, fireEvent, render, screen, waitFor} from "@testing-library/react";
+import DashboardButton from "../components/dashboard/DashboardButton";
+
+
+describe("Dashboard Button", () => {
+
+ each(["FaKiwiBird", "IoIosFootball", "FaGlobeAmericas",
+ "IoLogoGameControllerB", "FaPalette",
+ "FaRandom"]).test("the proper icon renderized", async (iconName) => {
+ const props = {
+ "label": "label",
+ "selectedButton": "label",
+ "onClick": () => {},
+ "iconName": iconName
+ };
+
+ render();
+
+ await waitFor( async () => {
+ expect(await screen.findByTestId(iconName)).toBeEnabled();
+ });
+ });
+
+});
\ No newline at end of file