diff --git a/webapp/e2e/features/discoveringCitiesGame.feature b/webapp/e2e/features/discoveringCitiesGame.feature index 6a63bd96..415f3d2e 100644 --- a/webapp/e2e/features/discoveringCitiesGame.feature +++ b/webapp/e2e/features/discoveringCitiesGame.feature @@ -3,9 +3,9 @@ Feature: Answer a question Scenario: Answering a question correctly Given A question When I click on the correct answer button - Then The button turns green + Then The selected answer is marked as right Scenario: Answering a question incorrectly Given A question When I click on an incorrect answer button - Then The button turns red \ No newline at end of file + Then The selected answer is marked as wrong \ No newline at end of file diff --git a/webapp/e2e/features/wiseMenStackGame.feature b/webapp/e2e/features/wiseMenStackGame.feature index 6a63bd96..da9e1fa6 100644 --- a/webapp/e2e/features/wiseMenStackGame.feature +++ b/webapp/e2e/features/wiseMenStackGame.feature @@ -3,9 +3,4 @@ Feature: Answer a question Scenario: Answering a question correctly Given A question When I click on the correct answer button - Then The button turns green - -Scenario: Answering a question incorrectly - Given A question - When I click on an incorrect answer button - Then The button turns red \ No newline at end of file + Then The selected answer is marked as right \ No newline at end of file diff --git a/webapp/e2e/steps/discoveringCitiesGame.steps.js b/webapp/e2e/steps/discoveringCitiesGame.steps.js index 5c1103b1..12ccb40e 100644 --- a/webapp/e2e/steps/discoveringCitiesGame.steps.js +++ b/webapp/e2e/steps/discoveringCitiesGame.steps.js @@ -65,52 +65,48 @@ defineFeature(feature, test => { test('Answering a question correctly', ({given,when,then}) => { given('A question', async () => { - //await expect(page.findByText('Which is the capital of Spain?')); - const question = await page.$['data-testid="question"']; - await expect(page).toMatchElement("div", { text: 'Which is the capital of Spain?'}); - expect(question).not.toBeNull(); - - const answers = await page.$x('//*[@data-testid="answer"]'); - expect(answers.length).toBe(4); + const question = await page.$['data-testid="question"']; + await expect(page).toMatchElement("div", { text: 'WHICH IS THE CAPITAL OF SPAIN?'}); + expect(question).not.toBeNull(); + + const answers = await page.$x('//*[contains(@data-testid, "answer")]'); + expect(answers.length).toBe(4); }); when('I click on the correct answer button', async () => { - const answers = await page.$x('(//*[@data-testid="answer"])[1]'); - await answers[0].click(); + const answers = await page.$x('//*[contains(@data-testid, "answer")]'); + await answers[0].click(); }); - then('The button turns green', async () => { - const answerButton = await page.$x('(//*[@data-testid="answer"])[1]'); - const textoBoton = await page.evaluate(button => button.innerText, answerButton[0]); - await expect(textoBoton).toMatch(/Madrid/i); - await expect(page).toMatchElement("button", { style: { color: 'green' } }); + then('The selected answer is marked as right', async () => { + const answer = await page.$x('//*[contains(@data-testid, "success")]'); + expect(answer.length).toBe(1); + const textoBoton = await page.evaluate(button => button.innerText, answer[0]); + await expect(textoBoton).toMatch(/Madrid/i); }); }) test('Answering a question incorrectly', ({given,when,then}) => { given('A question', async () => { - //await expect(page.findByText('Which is the capital of Spain?')); - const question = await page.$['data-testid="question"']; - await expect(page).toMatchElement("div", { text: 'Which is the capital of Spain?'}); - expect(question).not.toBeNull(); - - const answers = await page.$x('//*[@data-testid="answer"]'); - expect(answers.length).toBe(4); + const question = await page.$['data-testid="question"']; + await expect(page).toMatchElement("div", { text: 'WHICH IS THE CAPITAL OF SPAIN?'}); + expect(question).not.toBeNull(); + + const answers = await page.$x('//*[contains(@data-testid, "answer")]'); + expect(answers.length).toBe(4); }); when('I click on an incorrect answer button', async () => { - const answers = await page.$x('(//*[@data-testid="answer"])[2]'); - await answers[0].click(); + const answers = await page.$x('//*[contains(@data-testid, "answer")]'); + await answers[1].click(); }); - then('The button turns red', async () => { - const answerButton = await page.$x('(//*[@data-testid="answer"])[2]'); - const textoBoton = await page.evaluate(button => button.innerText, answerButton[0]); - await expect(textoBoton).toMatch(/Barcelona/i); - await expect(page).toMatchElement("button", { style: { color: 'red' } }); - await expect(page).toMatchElement("button", { style: { color: 'green' } }); - + then('The selected answer is marked as wrong', async () => { + const answer = await page.$x('//*[contains(@data-testid, "fail")]'); + expect(answer.length).toBe(1); + const textoBoton = await page.evaluate(button => button.innerText, answer[0]); + await expect(textoBoton).toMatch(/Barcelona/i); }); }) diff --git a/webapp/e2e/steps/wiseMenStackGame.steps.js b/webapp/e2e/steps/wiseMenStackGame.steps.js index 2c59525a..974dd592 100644 --- a/webapp/e2e/steps/wiseMenStackGame.steps.js +++ b/webapp/e2e/steps/wiseMenStackGame.steps.js @@ -10,7 +10,7 @@ defineFeature(feature, test => { beforeAll(async () => { - browser = process.env.GITHUB_ACTIONS + browser = process.env.GITHUB_ACTIONS ? await puppeteer.launch() : await puppeteer.launch({ headless: false, slowMo: 40 }); page = await browser.newPage(); @@ -65,77 +65,32 @@ defineFeature(feature, test => { test('Answering a question correctly', ({given,when,then}) => { given('A question', async () => { - const button = await page.$('[data-testid="start-button"]'); - await button.click(); + const button = await page.$('[data-testid="start-button"]'); + await button.click(); - //await expect(page.findByText('Which is the capital of Spain?')); - const question = await page.$['data-testid="question"']; - await expect(page).toMatchElement("div", { text: 'Which is the capital of Spain?'}); - expect(question).not.toBeNull(); - - const answers = await page.$x('//*[@data-testid="answer"]'); - expect(answers.length).toBe(2); + const question = await page.$['data-testid="question"']; + expect(question).not.toBeNull(); + await expect(page).toMatchElement("div", { text: 'WHICH IS THE CAPITAL OF SPAIN?'}); + + const answers = await page.$x('//*[contains(@data-testid, "answer")]'); + expect(answers.length).toBe(2); }); when('I click on the correct answer button', async () => { - const answers = await page.$x('//*[@data-testid="answer"]'); - const textoBoton1 = await page.evaluate(button => button.innerText, answers[0]); - if(textoBoton1 === "Madrid") { - await answers[0].click(); - } else { - await answers[1].click(); - } - }); - - then('The button turns green', async () => { - /*const answerButton = await page.$x('(//*[@data-testid="answer"])[1]'); - const textoBoton1 = await page.evaluate(button => button.innerText, answerButton[0]); - const textoBoton2 = await page.evaluate(button => button.innerText, answerButton[1]); - if(textoBoton1 === "Madrid") { - await expect(textoBoton1).toMatch(/Madrid/i); - } else { - await expect(textoBoton2).toMatch(/Madrid/i); - }*/ - await expect(page).toMatchElement("button", { style: { color: 'green' } }); - }); - }) - - test('Answering a question incorrectly', ({given,when,then}) => { - - given('A question', async () => { - const button = await page.$('[data-testid="start-button"]'); - await button.click(); - - //await expect(page.findByText('Which is the capital of Spain?')); - const question = await page.$['data-testid="question"']; - await expect(page).toMatchElement("div", { text: 'Which is the capital of Spain?'}); - expect(question).not.toBeNull(); - - const answers = await page.$x('//*[@data-testid="answer"]'); - expect(answers.length).toBe(2); - }); - - when('I click on an incorrect answer button', async () => { - const answers = await page.$x('//*[@data-testid="answer"]'); - const textoBoton1 = await page.evaluate(button => button.innerText, answers[0]); - if(textoBoton1 !== "Madrid") { - await answers[0].click(); - } else { - await answers[1].click(); - } + const answers = await page.$x('//*[contains(@data-testid, "answer")]'); + const textoBoton1 = await page.evaluate(button => button.innerText, answers[0]); + if(textoBoton1 === "MADRID") { + await answers[0].click(); + } else { + await answers[1].click(); + } }); - then('The button turns red', async () => { - /*const answerButton = await page.$x('(//*[@data-testid="answer"])[2]'); - const textoBoton1 = await page.evaluate(button => button.innerText, answerButton[0]); - const textoBoton2 = await page.evaluate(button => button.innerText, answerButton[1]); - if(textoBoton1 !== "Madrid") { - await expect(textoBoton1).not.toMatch(/Madrid/i); - } else { - await expect(textoBoton2).toMatch(/Madrid/i); - }*/ - await expect(page).toMatchElement("button", { style: { color: 'red' } }); - await expect(page).toMatchElement("button", { style: { color: 'green' } }); + then('The selected answer is marked as right', async () => { + const answer = await page.$x('//*[contains(@data-testid, "success")]'); + expect(answer.length).toBe(1); + const textoBoton = await page.evaluate(button => button.innerText, answer[0]); + await expect(textoBoton).toMatch(/Madrid/i); }); }) diff --git a/webapp/src/App.js b/webapp/src/App.js index eb8254d5..d5c768ba 100644 --- a/webapp/src/App.js +++ b/webapp/src/App.js @@ -6,22 +6,22 @@ import NavBar from './components/NavBar'; import Footer from './components/Footer'; import Home from './pages/Home'; import Homepage from './pages/Homepage'; -import Game from './pages/Game'; -import DiscoveringCitiesGame from './pages/DiscoveringCitiesGame'; -import WiseMenStackGame from './pages/WiseMenStackGame'; +import Game from './pages/games/Game'; +import DiscoveringCitiesGame from './pages/games/DiscoveringCitiesGame'; +import WiseMenStackGame from './pages/games/WiseMenStackGame'; import Groups from './pages/Groups'; import GroupDetails from './pages/GroupDetails'; import Statistics from './pages/Statistics'; import Ranking from './pages/Ranking' import Profile from './pages/Profile' -import MultiplayerRoom from './pages/MultiplayerRoom'; -import MultiplayerGame from './pages/MultiplayerGame'; -import TheChallengeGame from './pages/TheChallengeGame'; -import WarmQuestion from './pages/WarmQuestionGame'; +import MultiplayerRoom from './pages/games/MultiplayerRoom'; +import MultiplayerGame from './pages/games/MultiplayerGame'; +import TheChallengeGame from './pages/games/TheChallengeGame'; +import WarmQuestionGame from './pages/games/WarmQuestionGame'; import {Route, Routes} from 'react-router-dom'; import { createTheme, ThemeProvider } from '@mui/material/styles'; import { Box } from '@mui/material'; -import PrivateRoute from './PrivateRoute'; +import PrivateRoute from './pages/PrivateRoute'; import NotFound from './pages/NotFound'; const theme = createTheme({ @@ -66,7 +66,7 @@ function App() { }/> }/> }/> - }/> + }/> }/> } /> }/> diff --git a/webapp/src/__tests__/components/Chat.test.js b/webapp/src/__tests__/components/Chat.test.js index 999e419a..6e786cf0 100644 --- a/webapp/src/__tests__/components/Chat.test.js +++ b/webapp/src/__tests__/components/Chat.test.js @@ -33,8 +33,8 @@ describe('Chat component', () => { test('sends a message', async () => { - jest.mock('../../pages/MultiplayerRoom', () => ({ - ...jest.requireActual('../../pages/MultiplayerRoom'), + jest.mock('../../pages/games/MultiplayerRoom', () => ({ + ...jest.requireActual('../../pages/games/MultiplayerRoom'), generateRoomCode: jest.fn().mockReturnValue('AAAAA'), })); diff --git a/webapp/src/__tests__/components/NavBar.test.js b/webapp/src/__tests__/components/NavBar.test.js index a19aa416..cd547778 100644 --- a/webapp/src/__tests__/components/NavBar.test.js +++ b/webapp/src/__tests__/components/NavBar.test.js @@ -26,8 +26,10 @@ describe('NavBar component', () => { ); - const logo = screen.getByAltText('Logo'); - await expect(logo).toBeInTheDocument(); + const logo = screen.getAllByAltText('Logo'); + // There should be the one for mobile and the one for normal devices + await expect(logo[0]).toBeInTheDocument(); + await expect(logo[1]).toBeInTheDocument(); }); it('should render log-in option', async () => { @@ -42,6 +44,7 @@ describe('NavBar component', () => { await expect(logIn).toBeInTheDocument(); }); + // As there s a link for the menu (mobiles version) and in the propper nav, we have to check 2 elements appear it('should render navigation links', async () => { render( diff --git a/webapp/src/__tests__/pages/TheChallengeGame.test.js b/webapp/src/__tests__/pages/TheChallengeGame.test.js deleted file mode 100644 index 9627bba8..00000000 --- a/webapp/src/__tests__/pages/TheChallengeGame.test.js +++ /dev/null @@ -1,153 +0,0 @@ -import React from 'react'; -import { render, fireEvent, screen, waitFor } from '@testing-library/react'; -import { SessionContext } from '../../SessionContext'; -import { BrowserRouter as Router } from 'react-router-dom'; -import axios from 'axios'; -import MockAdapter from 'axios-mock-adapter'; -import '../../localize/i18n'; -import Game from '../../pages/TheChallengeGame'; - -const mockAxios = new MockAdapter(axios); - -describe('Game component', () => { - beforeEach(() => { - mockAxios.reset(); - // Mockear respuestas de la API - mockAxios.onGet(`http://localhost:8000/questions/en/Geography`).reply(200, - [ - { - question: 'Which is the capital of Spain?', - options: ['Madrid', 'Barcelona', 'Paris', 'London'], - correctAnswer: 'Madrid' - } - ] - ); - - mockAxios.onPut(`http://localhost:8000/statistics`).reply(200, { success: true }); - mockAxios.onPut(`http://localhost:8000/questionsRecord`).reply(200, { success: true }); - - }); - - it('should render configuration window and start game', async () => { - render( - - - - - - ); - - // Espera a que aparezca la ventana de configuración - await waitFor(() => screen.getByText('Game Configuration')); - - // Simula la configuración del juego - const increaseButtons = screen.getAllByRole('button', { name: '+' }); - fireEvent.click(increaseButtons[0]); // Aumenta el número de rondas - fireEvent.click(increaseButtons[1]); // Aumenta el tiempo por pregunta - //fireEvent.change(screen.getByLabelText('Category:'), { target: { value: 'Sports' } }); - - // Inicia el juego - fireEvent.click(screen.getByRole('button', { name: 'Start game' })); - - // Espera a que aparezca la pregunta - await waitFor(() => screen.getByText('Which is the capital of Spain?'.toUpperCase())); - - // Verifica que el juego haya comenzado correctamente mostrando la pregunta y las opciones - expect(screen.getByText('Which is the capital of Spain?'.toUpperCase())).toBeInTheDocument(); - expect(screen.getByText('Madrid')).toBeInTheDocument(); - expect(screen.getByText('Barcelona')).toBeInTheDocument(); - expect(screen.getByText('Paris')).toBeInTheDocument(); - expect(screen.getByText('London')).toBeInTheDocument(); - }); - - it('should guess correct answer', async () => { - render( - - - - - - ); - - // Espera a que aparezca la ventana de configuración - await waitFor(() => screen.getByText('Game Configuration')); - - // Simula la configuración del juego - const increaseButtons = screen.getAllByRole('button', { name: '+' }); - fireEvent.click(increaseButtons[0]); // Aumenta el número de rondas - - // Inicia el juego - fireEvent.click(screen.getByRole('button', { name: 'Start game' })); - - // waits for the question to appear - await waitFor(() => screen.getByText('Which is the capital of Spain?'.toUpperCase())); - const correctAnswer = screen.getByRole('button', { name: 'Madrid' }); - - expect(screen.findByTestId("anwer0")); - //selects correct answer - fireEvent.click(correctAnswer); - expect(screen.findByTestId("succes0")); - - }); - - - it('should choose incorrect answer', async () => { - render( - - - - - - ); - - // Espera a que aparezca la ventana de configuración - await waitFor(() => screen.getByText('Game Configuration')); - - // Simula la configuración del juego - const increaseButtons = screen.getAllByRole('button', { name: '+' }); - fireEvent.click(increaseButtons[0]); // Aumenta el número de rondas - - // Inicia el juego - fireEvent.click(screen.getByRole('button', { name: 'Start game' })); - - // waits for the question to appear - await waitFor(() => screen.getByText('Which is the capital of Spain?'.toUpperCase())); - const incorrectAnswer = screen.getByRole('button', { name: 'Barcelona' }); - - expect(screen.findByTestId("anwer1")); - //selects correct answer - fireEvent.click(incorrectAnswer); - expect(screen.findByTestId("failure1")); - - }); - - it('should not answer the question', async () => { - render( - - - - - - ); - - // Espera a que aparezca la ventana de configuración - await waitFor(() => screen.getByText('Game Configuration')); - - // Simula la configuración del juego - const increaseButtons = screen.getAllByRole('button', { name: '+' }); - fireEvent.click(increaseButtons[0]); // Aumenta el número de rondas - - // Inicia el juego - fireEvent.click(screen.getByRole('button', { name: 'Start game' })); - - // waits for the question to appear - await waitFor(() => screen.getByText('Which is the capital of Spain?'.toUpperCase())); - - setTimeout(() => { - // Comprobamos que el callback ha sido llamado después del tiempo especificado - done(); // Llamamos a done para indicar que la prueba ha terminado - }, 4000); - - }, 4500); - -}); \ No newline at end of file diff --git a/webapp/src/__tests__/pages/WiseMenStackGame.test.js b/webapp/src/__tests__/pages/WiseMenStackGame.test.js deleted file mode 100644 index ff4961cf..00000000 --- a/webapp/src/__tests__/pages/WiseMenStackGame.test.js +++ /dev/null @@ -1,131 +0,0 @@ -import React from 'react'; -import { render, fireEvent, screen, waitFor } from '@testing-library/react'; -import { SessionContext } from '../../SessionContext'; // Importa el contexto necesario -import { BrowserRouter as Router } from 'react-router-dom'; -import axios from 'axios'; -import MockAdapter from 'axios-mock-adapter'; -import Game from '../../pages/WiseMenStackGame'; -import '../../localize/i18n'; -import { expect } from 'expect-puppeteer'; - -const mockAxios = new MockAdapter(axios); - -describe('Wise Men Stack Game component', () => { - beforeEach(() => { - mockAxios.reset(); - // Mock the axios.post request to simulate a successful response - mockAxios.onGet('http://localhost:8000/questions/en/Geography').reply(200, - [{ - question: 'Which is the capital of Spain?', - options: ['Madrid', 'Barcelona', 'Paris', 'London'], - correctAnswer: 'Madrid', - categories: ['Geography'], - language: 'en' - }] - ); - - mockAxios.onPut('http://localhost:8000/statistics').reply(200, { success: true }); - mockAxios.onPut('http://localhost:8000/user/questionsRecord').reply(200, { success: true }); - - }); - - it('should render configuration, question, answers and other ', async () => { - render( - - - - - - ); - - await waitFor(() => screen.getByText('Wise Men Stack')); - - const button = screen.getByText('Start game'); - fireEvent.click(button); - - //expect(screen.getByRole('progressbar')); - expect(screen.findByText('1')); - expect(screen.findByText('Question 1')); - //expect(screen.findByText('1/3')); - - // waits for the question to appear - await waitFor(() => screen.getByText('Which is the capital of Spain?')); - - expect(screen.findByText('Which is the capital of Spain?')); - expect(screen.findByText('Madrid')); - - }); - - it('should guess correct answer', async () => { - render( - - - - - - ); - await waitFor(() => screen.getByText('Wise Men Stack')); - - const button = screen.getByText('Start game'); - fireEvent.click(button); - - // waits for the question to appear - await waitFor(() => screen.getByText('Which is the capital of Spain?')); - const correctAnswer = screen.getByRole('button', { name: 'Madrid' }); - - expect(correctAnswer).not.toHaveStyle({ backgroundColor: 'green' }); - - //selects correct answer - fireEvent.click(correctAnswer); - - //expect(screen.findByText('1')).toHaveStyle({ backgroundColor: 'lightgreen' }); - - expect(correctAnswer).toHaveStyle({ backgroundColor: 'green' }); - - }); - - - it('should choose incorrect answer', async () => { - render( - - - - - - ); - await waitFor(() => screen.getByText('Wise Men Stack')); - - const button = screen.getByText('Start game'); - fireEvent.click(button); - - // waits for the question to appear - await waitFor(() => screen.getByText('Which is the capital of Spain?')); - const buttons = screen.getAllByRole('button'); - expect(buttons.length).toBe(2); - }); - - it('should not answer the question', async () => { - render( - - - - - - ); - - await waitFor(() => screen.getByText('Wise Men Stack')); - - const button = screen.getByText('Start game'); - fireEvent.click(button); - - // waits for the question to appear - await waitFor(() => screen.getByText('Which is the capital of Spain?')); - - setTimeout(() => { - // Comprobamos que el callback ha sido llamado después del tiempo especificado - done(); // Llamamos a done para indicar que la prueba ha terminado - }, 4000); - - }, 4500); - -}); diff --git a/webapp/src/__tests__/pages/DiscoveringCitiesGame.test.js b/webapp/src/__tests__/pages/games/DiscoveringCitiesGame.test.js similarity index 62% rename from webapp/src/__tests__/pages/DiscoveringCitiesGame.test.js rename to webapp/src/__tests__/pages/games/DiscoveringCitiesGame.test.js index ff54afdc..fd0336f2 100644 --- a/webapp/src/__tests__/pages/DiscoveringCitiesGame.test.js +++ b/webapp/src/__tests__/pages/games/DiscoveringCitiesGame.test.js @@ -1,15 +1,15 @@ import React from 'react'; import { render, fireEvent, screen, waitFor } from '@testing-library/react'; -import { SessionContext } from '../../SessionContext'; // Importa el contexto necesario +import { SessionContext } from '../../../SessionContext'; import { BrowserRouter as Router } from 'react-router-dom'; import axios from 'axios'; import MockAdapter from 'axios-mock-adapter'; -import Game from '../../pages/DiscoveringCitiesGame'; -import '../../localize/i18n'; +import DiscoveringCitiesGame from '../../../pages/games/DiscoveringCitiesGame'; +import '../../../localize/i18n'; const mockAxios = new MockAdapter(axios); - -describe('Game component', () => { + +describe('Discovering Cities component', () => { beforeEach(() => { mockAxios.reset(); // Mock the axios.post request to simulate a successful response @@ -32,25 +32,21 @@ describe('Game component', () => { render( - + ); expect(screen.getByRole('progressbar')); - //expect(screen.findByText('1')); - //expect(screen.findByText('1/5')); // waits for the question to appear - await waitFor(() => screen.getByText('Which is the capital of Spain?')); + await waitFor(() => screen.getByTestId('question')); - expect(screen.findByText('Which is the capital of Spain?')); + expect(screen.getByTestId('question')); expect(screen.findByText('Madrid')); expect(screen.findByText('Barcelona')); expect(screen.findByText('Paris')); expect(screen.findByText('London')); - - expect(screen.getByRole('button', { name: /Pause/i })); }); @@ -58,23 +54,21 @@ describe('Game component', () => { render( - + ); // waits for the question to appear - await waitFor(() => screen.getByText('Which is the capital of Spain?')); + await waitFor(() => screen.getByTestId('question')); const correctAnswer = screen.getByRole('button', { name: 'Madrid' }); - expect(correctAnswer).not.toHaveStyle({ backgroundColor: 'green' }); + expect(correctAnswer).toHaveStyle({ backgroundColor: 'rgb(0, 102, 153);' }); //selects correct answer fireEvent.click(correctAnswer); - //expect(screen.findByText('1')).toHaveStyle({ backgroundColor: 'lightgreen' }); - - expect(correctAnswer).toHaveStyle({ backgroundColor: 'green' }); + expect(correctAnswer).toHaveStyle({ backgroundColor: 'rgb(51, 153, 102);' }); }); @@ -82,21 +76,20 @@ describe('Game component', () => { render( - + ); // waits for the question to appear - await waitFor(() => screen.getByText('Which is the capital of Spain?')); + await waitFor(() => screen.getByTestId('question')); const incorrectAnswer = screen.getByRole('button', { name: 'Barcelona' }); - expect(incorrectAnswer).not.toHaveStyle({ backgroundColor: 'red' }); + expect(incorrectAnswer).toHaveStyle({ backgroundColor: 'rgb(0, 102, 153);' }); //selects correct answer fireEvent.click(incorrectAnswer); - expect(incorrectAnswer).toHaveStyle({ backgroundColor: 'red' }); - //expect(screen.findByText('1')).toHaveStyle({ backgroundColor: 'salmon' }); + expect(incorrectAnswer).toHaveStyle({ backgroundColor: 'rgb(153, 0, 0);' }); }); @@ -104,13 +97,13 @@ describe('Game component', () => { render( - + ); // waits for the question to appear - await waitFor(() => screen.getByText('Which is the capital of Spain?')); + await waitFor(() => screen.getByTestId('question')); setTimeout(() => { // Comprobamos que el callback ha sido llamado después del tiempo especificado @@ -119,4 +112,24 @@ describe('Game component', () => { }, 4500); + it('should pause and resume the game after answering a question', async () => { + render( + + + + + + ); + + await waitFor(() => screen.getByTestId('question')); + + fireEvent.click(screen.getByRole('button', { name: 'Madrid' })); + + await waitFor(() => screen.getByTestId('pause')); + + fireEvent.click(screen.getByTestId('pause')); + + expect(screen.getByTestId('play')).toBeInTheDocument(); + }); + }); diff --git a/webapp/src/__tests__/pages/Game.test.js b/webapp/src/__tests__/pages/games/Game.test.js similarity index 86% rename from webapp/src/__tests__/pages/Game.test.js rename to webapp/src/__tests__/pages/games/Game.test.js index c339aa55..2aa4e4d2 100644 --- a/webapp/src/__tests__/pages/Game.test.js +++ b/webapp/src/__tests__/pages/games/Game.test.js @@ -1,11 +1,11 @@ import React from 'react'; import { render, fireEvent, screen, waitFor } from '@testing-library/react'; -import { SessionContext } from '../../SessionContext'; // Importa el contexto necesario +import { SessionContext } from '../../../SessionContext'; // Importa el contexto necesario import { BrowserRouter as Router } from 'react-router-dom'; import axios from 'axios'; import MockAdapter from 'axios-mock-adapter'; -import Game from '../../pages/Game'; -import '../../localize/i18n'; +import Game from '../../../pages/games/Game'; +import '../../../localize/i18n'; const mockAxios = new MockAdapter(axios); @@ -35,11 +35,7 @@ describe('Game component', () => { ); }); - it('should render question, answers and other ', async () => { - expect(screen.getByRole('progressbar')); - expect(screen.findByText('1')); - //expect(screen.findByText('1/3')); - + it('should render question', async () => { // waits for the question to appear await waitFor(() => screen.getByText('Which is the capital of Spain?'.toUpperCase())); @@ -94,6 +90,12 @@ describe('Game component', () => { expect(pauseButton); fireEvent.click(pauseButton); expect(screen.getByTestId("play")); - }) + }); + + it('should render progress bar', async () => { + await waitFor(() => screen.getByText('Which is the capital of Spain?'.toUpperCase())); + const progressBar = screen.getByTestId('prog_bar0'); + await expect(progressBar).toBeInTheDocument(); + }); }); diff --git a/webapp/src/__tests__/pages/MultiplayerGame.test.js b/webapp/src/__tests__/pages/games/MultiplayerGame.test.js similarity index 59% rename from webapp/src/__tests__/pages/MultiplayerGame.test.js rename to webapp/src/__tests__/pages/games/MultiplayerGame.test.js index eb828baf..9dc95380 100644 --- a/webapp/src/__tests__/pages/MultiplayerGame.test.js +++ b/webapp/src/__tests__/pages/games/MultiplayerGame.test.js @@ -1,10 +1,10 @@ import React from 'react'; import { render, fireEvent, screen, waitFor } from '@testing-library/react'; -import { SessionContext } from '../../SessionContext'; +import { SessionContext } from '../../../SessionContext'; import { BrowserRouter, useLocation } from 'react-router-dom'; -import MultiplayerGame from '../../pages/MultiplayerGame'; +import MultiplayerGame from '../../../pages/games/MultiplayerGame'; import io from 'socket.io-client'; -import '../../localize/i18n'; +import '../../../localize/i18n'; jest.mock('react-router-dom', () => ({ ...jest.requireActual('react-router-dom'), @@ -24,7 +24,21 @@ describe('Game component', () => { let socket; beforeEach(() => { - socket = io(); + socket = io(); + useLocation.mockReturnValue({ + state: { + gameQuestions: mockgameQuestions, + roomCode: "AAAAA", + }, + }) + + render( + + + + + + ); }); const questionObject = { @@ -46,95 +60,47 @@ describe('Game component', () => { const mockgameQuestions = generateQuestionArray(questionObject, 3); it('should render with recieved room code and questions ', async () => { - - //mock info that room sent to the game - useLocation.mockReturnValue({ - state: { - gameQuestions: mockgameQuestions, - roomCode: "AAAAA", - }, - }) - - render( - - - - - - ); - - expect(screen.findByText('1')); - expect(screen.findByText('1/3')); - // waits for the question to appear - await waitFor(() => screen.getByText('Which is the capital of Spain?')); + await waitFor(() => screen.getByTestId('question')); - expect(screen.findByText('Which is the capital of Spain?')); + expect(screen.getByTestId('question')); expect(screen.findByText('Madrid')); expect(screen.findByText('Barcelona')); expect(screen.findByText('Paris')); expect(screen.findByText('London')); }); - it('should guess correct answer', async () => { - - useLocation.mockReturnValue({ - state: { - gameQuestions: mockgameQuestions, - roomCode: "AAAAA", - }, - }) - - render( - - - - - - ); - + it('should guess correct answer', async () => { // waits for the question to appear - await waitFor(() => screen.getByText('Which is the capital of Spain?')); + await waitFor(() => screen.getByTestId('question')); const correctAnswer = screen.getByRole('button', { name: 'Madrid' }); - expect(correctAnswer).not.toHaveStyle({ backgroundColor: 'green' }); + expect(correctAnswer).toHaveStyle({ backgroundColor: 'rgb(0, 102, 153);' }); //selects correct answer fireEvent.click(correctAnswer); - expect(correctAnswer).toHaveStyle({ backgroundColor: 'green' }); - + expect(correctAnswer).toHaveStyle({ backgroundColor: 'rgb(51, 153, 102);' }); }); it('should choose incorrect answer', async () => { - - useLocation.mockReturnValue({ - state: { - gameQuestions: mockgameQuestions, - roomCode: "AAAAA", - }, - }) - - render( - - - - - - ); - // waits for the question to appear - await waitFor(() => screen.getByText('Which is the capital of Spain?')); + await waitFor(() => screen.getByTestId('question')); const incorrectAnswer = screen.getByRole('button', { name: 'Barcelona' }); - expect(incorrectAnswer).not.toHaveStyle({ backgroundColor: 'red' }); + expect(incorrectAnswer).toHaveStyle({ backgroundColor: 'rgb(0, 102, 153);' }); //selects correct answer fireEvent.click(incorrectAnswer); - expect(incorrectAnswer).toHaveStyle({ backgroundColor: 'red' }); - + expect(incorrectAnswer).toHaveStyle({ backgroundColor: 'rgb(153, 0, 0);' }); }); + it('should render progress bar', async () => { + await waitFor(() => screen.getByText('Which is the capital of Spain?'.toUpperCase())); + const progressBar = screen.getByTestId('prog_bar0'); + await expect(progressBar).toBeInTheDocument(); + }) + }); diff --git a/webapp/src/__tests__/pages/MultiplayerRoom.test.js b/webapp/src/__tests__/pages/games/MultiplayerRoom.test.js similarity index 88% rename from webapp/src/__tests__/pages/MultiplayerRoom.test.js rename to webapp/src/__tests__/pages/games/MultiplayerRoom.test.js index 29a10208..50508669 100644 --- a/webapp/src/__tests__/pages/MultiplayerRoom.test.js +++ b/webapp/src/__tests__/pages/games/MultiplayerRoom.test.js @@ -1,10 +1,10 @@ import React from 'react'; import { render, fireEvent, waitFor, screen } from '@testing-library/react'; -import MultiplayerRoom from '../../pages/MultiplayerRoom'; +import MultiplayerRoom from '../../../pages/games/MultiplayerRoom'; import io from 'socket.io-client'; -import { SessionContext } from '../../SessionContext'; +import { SessionContext } from '../../../SessionContext'; import { BrowserRouter as Router } from 'react-router-dom'; -import '../../localize/i18n'; +import '../../../localize/i18n'; //mock some socket behaviour jest.mock('socket.io-client', () => { @@ -34,8 +34,8 @@ describe('MultiplayerRoom component', () => { test('creates a room', async () => { - jest.mock('../../pages/MultiplayerRoom', () => ({ - ...jest.requireActual('../../pages/MultiplayerRoom'), + jest.mock('../../../pages/games/MultiplayerRoom', () => ({ + ...jest.requireActual('../../../pages/games/MultiplayerRoom'), generateRoomCode: jest.fn().mockReturnValue('AAAAA'), })); diff --git a/webapp/src/__tests__/pages/games/TheChallengeGame.test.js b/webapp/src/__tests__/pages/games/TheChallengeGame.test.js new file mode 100644 index 00000000..039dd58f --- /dev/null +++ b/webapp/src/__tests__/pages/games/TheChallengeGame.test.js @@ -0,0 +1,143 @@ +import React from 'react'; +import { render, fireEvent, screen, waitFor } from '@testing-library/react'; +import { SessionContext } from '../../../SessionContext'; +import { BrowserRouter as Router } from 'react-router-dom'; +import axios from 'axios'; +import MockAdapter from 'axios-mock-adapter'; +import '../../../localize/i18n'; +import TheChallengeGame from '../../../pages/games/TheChallengeGame'; + +const mockAxios = new MockAdapter(axios); + +describe('The Challenge component', () => { + beforeEach(() => { + mockAxios.reset(); + // Mockear respuestas de la API + mockAxios.onGet(`http://localhost:8000/questions/en/Geography`).reply(200, + [ + { + question: 'Which is the capital of Spain?', + options: ['Madrid', 'Barcelona', 'Paris', 'London'], + correctAnswer: 'Madrid' + } + ] + ); + + mockAxios.onPut(`http://localhost:8000/statistics`).reply(200, { success: true }); + mockAxios.onPut(`http://localhost:8000/questionsRecord`).reply(200, { success: true }); + + render( + + + + + + ); + }); + + it('should render configuration window and start game', async () => { + // Espera a que aparezca la ventana de configuración + await waitFor(() => screen.getByText('GAME CONFIGURATION')); + + // Simula la configuración del juego + const increaseButton = screen.getByTestId('addRound'); + fireEvent.click(increaseButton); // Aumenta el número de rondas + + // Inicia el juego + fireEvent.click(screen.getByRole('button', { name: 'Start Game' })); + + // Espera a que aparezca la pregunta + await waitFor(() => screen.getByTestId('question')); + + // Verifica que el juego haya comenzado correctamente mostrando la pregunta y las opciones + expect(screen.getByTestId('question')).toBeInTheDocument(); + expect(screen.getByText('Madrid')).toBeInTheDocument(); + expect(screen.getByText('Barcelona')).toBeInTheDocument(); + expect(screen.getByText('Paris')).toBeInTheDocument(); + expect(screen.getByText('London')).toBeInTheDocument(); + }); + + it('should guess correct answer', async () => { + // Espera a que aparezca la ventana de configuración + await waitFor(() => screen.getByText('GAME CONFIGURATION')); + + // Simula la configuración del juego + const increaseRoundButton = screen.getByTestId('addRound'); + fireEvent.click(increaseRoundButton); // Aumenta el número de rondas + const increaseSecondsButton = screen.getByTestId('addSecond'); + fireEvent.click(increaseSecondsButton); // Aumenta los segundos por ronda + + // Inicia el juego + fireEvent.click(screen.getByRole('button', { name: 'Start Game' })); + + // waits for the question to appear + await waitFor(() => screen.getByTestId('question')); + const correctAnswer = screen.getByRole('button', { name: 'Madrid' }); + + //selects correct answer + fireEvent.click(correctAnswer); + + expect(screen.findByTestId("success0")); + + }); + + + it('should choose incorrect answer', async () => { + // Espera a que aparezca la ventana de configuración + await waitFor(() => screen.getByText('GAME CONFIGURATION')); + + // Simula la configuración del juego + const increaseButton = screen.getByTestId('addRound'); + fireEvent.click(increaseButton); // Aumenta el número de rondas + + // Inicia el juego + fireEvent.click(screen.getByRole('button', { name: 'Start Game' })); + + // waits for the question to appear + await waitFor(() => screen.getByTestId('question')); + const incorrectAnswer = screen.getByRole('button', { name: 'Barcelona' }); + + //selects correct answer + fireEvent.click(incorrectAnswer); + expect(screen.findByTestId("fail1")); + + }); + + it('should not answer the question', async () => { + // Espera a que aparezca la ventana de configuración + await waitFor(() => screen.getByText('GAME CONFIGURATION')); + + // Simula la configuración del juego + const increaseButton = screen.getByTestId('addRound'); + fireEvent.click(increaseButton); // Aumenta el número de rondas + + // Inicia el juego + fireEvent.click(screen.getByRole('button', { name: 'Start Game' })); + + // waits for the question to appear + await waitFor(() => screen.getByTestId('question')); + + setTimeout(() => { + // Comprobamos que el callback ha sido llamado después del tiempo especificado + done(); // Llamamos a done para indicar que la prueba ha terminado + }, 4000); + + }, 4500); + + it('should pause and resume the game after answering a question', async () => { + await waitFor(() => screen.getByText('GAME CONFIGURATION')); + fireEvent.click(screen.getByTestId('addRound')); + fireEvent.click(screen.getByRole('button', { name: 'Start Game' })); + + await waitFor(() => screen.getByTestId('question')); + + fireEvent.click(screen.getByRole('button', { name: 'Madrid' })); + + await waitFor(() => screen.getByTestId('pause')); + + fireEvent.click(screen.getByTestId('pause')); + + expect(screen.getByTestId('play')).toBeInTheDocument(); + }); + +}); \ No newline at end of file diff --git a/webapp/src/__tests__/pages/WarmQuestionGame.test.js b/webapp/src/__tests__/pages/games/WarmQuestionGame.test.js similarity index 55% rename from webapp/src/__tests__/pages/WarmQuestionGame.test.js rename to webapp/src/__tests__/pages/games/WarmQuestionGame.test.js index 6297a0d5..0711d1e4 100644 --- a/webapp/src/__tests__/pages/WarmQuestionGame.test.js +++ b/webapp/src/__tests__/pages/games/WarmQuestionGame.test.js @@ -1,14 +1,14 @@ import React from 'react'; import { render, fireEvent, screen, waitFor } from '@testing-library/react'; -import { SessionContext } from '../../SessionContext'; +import { SessionContext } from '../../../SessionContext'; import { BrowserRouter as Router } from 'react-router-dom'; import axios from 'axios'; import MockAdapter from 'axios-mock-adapter'; -import Game from '../../pages/WarmQuestionGame'; +import WarmQuestionGame from '../../../pages/games/WarmQuestionGame'; const mockAxios = new MockAdapter(axios); -describe('Game component', () => { +describe('WarmQuestionGame component', () => { beforeEach(() => { mockAxios.reset(); // Mock the axios.post request to simulate a successful response @@ -24,24 +24,22 @@ describe('Game component', () => { mockAxios.onPut('http://localhost:8000/statistics').reply(200, { success: true }); mockAxios.onPut('http://localhost:8000/questionsRecord').reply(200, { success: true }); - }); - it('should render question, answers and other', async () => { render( - - - - - + + + + + ); + }); + it('should render question, answers and other', async () => { // Espera a que aparezca la pregunta - await waitFor(() => screen.getByText('Which is the capital of Spain?')); - - expect(screen.findByText('1')); + await waitFor(() => screen.getByTestId('question')); // Verifica que el juego haya comenzado correctamente mostrando la pregunta y las opciones - expect(screen.getByText('Which is the capital of Spain?')).toBeInTheDocument(); + expect(screen.getByTestId('question')).toBeInTheDocument(); expect(screen.getByText('Madrid')).toBeInTheDocument(); expect(screen.getByText('Barcelona')).toBeInTheDocument(); expect(screen.getByText('Paris')).toBeInTheDocument(); @@ -49,64 +47,37 @@ describe('Game component', () => { }); it('should guess correct answer', async () => { - render( - - - - - - ); - // waits for the question to appear - await waitFor(() => screen.getByText('Which is the capital of Spain?')); + await waitFor(() => screen.getByTestId('question')); const correctAnswer = screen.getByRole('button', { name: 'Madrid' }); - expect(correctAnswer).not.toHaveStyle({ backgroundColor: 'green' }); + expect(correctAnswer).toHaveStyle({ backgroundColor: 'rgb(0, 102, 153);' }); //selects correct answer fireEvent.click(correctAnswer); - //expect(screen.findByText('1')).toHaveStyle({ backgroundColor: 'lightgreen' }); - - expect(correctAnswer).toHaveStyle({ backgroundColor: 'green' }); + expect(correctAnswer).toHaveStyle({ backgroundColor: 'rgb(51, 153, 102);' }); }); it('should choose incorrect answer', async () => { - render( - - - - - - ); - // waits for the question to appear - await waitFor(() => screen.getByText('Which is the capital of Spain?')); + await waitFor(() => screen.getByTestId('question')); const incorrectAnswer = screen.getByRole('button', { name: 'Barcelona' }); - expect(incorrectAnswer).not.toHaveStyle({ backgroundColor: 'red' }); + expect(incorrectAnswer).toHaveStyle({ backgroundColor: 'rgb(0, 102, 153);' }); //selects correct answer fireEvent.click(incorrectAnswer); - expect(incorrectAnswer).toHaveStyle({ backgroundColor: 'red' }); - //expect(screen.findByText('1')).toHaveStyle({ backgroundColor: 'salmon' }); + expect(incorrectAnswer).toHaveStyle({ backgroundColor: 'rgb(153, 0, 0);' }); }); it('should not answer the question', async () => { - render( - - - - - - ); - // waits for the question to appear - await waitFor(() => screen.getByText('Which is the capital of Spain?')); + await waitFor(() => screen.getByTestId('question')); setTimeout(() => { // Comprobamos que el callback ha sido llamado después del tiempo especificado @@ -116,29 +87,35 @@ describe('Game component', () => { }, 4500); it('should pass the question', async () => { - render( - - - - - - ); - // waits for the question to appear - await waitFor(() => screen.getByText('Which is the capital of Spain?')); + await waitFor(() => screen.getByTestId('question')); const correctAnswer = screen.getByRole('button', { name: 'Madrid' }); const skip = screen.getByRole('button', { name: 'Skip' }); - expect(correctAnswer).not.toHaveStyle({ backgroundColor: 'green' }); + expect(correctAnswer).toHaveStyle({ backgroundColor: 'rgb(0, 102, 153);' }); //selects correct answer fireEvent.click(skip); - //expect(screen.findByText('1')).toHaveStyle({ backgroundColor: 'lightgreen' }); - - expect(correctAnswer).toHaveStyle({ backgroundColor: 'green' }); + expect(correctAnswer).toHaveStyle({ backgroundColor: 'rgb(51, 153, 102);' }); }); + it('should render pause & play buttons when answered', async () => { + await waitFor(() => screen.getByText('Which is the capital of Spain?'.toUpperCase())); + const correctAnswer = screen.getByRole('button', { name: 'Madrid' }); + fireEvent.click(correctAnswer); + + const pauseButton = screen.getByTestId("pause"); + expect(pauseButton); + fireEvent.click(pauseButton); + expect(screen.getByTestId("play")); + }) + + it('should render progress bar', async () => { + await waitFor(() => screen.getByText('Which is the capital of Spain?'.toUpperCase())); + const progressBar = screen.getByTestId('prog_bar0'); + await expect(progressBar).toBeInTheDocument(); + }) }); \ No newline at end of file diff --git a/webapp/src/__tests__/pages/games/WiseMenStackGame.test.js b/webapp/src/__tests__/pages/games/WiseMenStackGame.test.js new file mode 100644 index 00000000..55588acb --- /dev/null +++ b/webapp/src/__tests__/pages/games/WiseMenStackGame.test.js @@ -0,0 +1,144 @@ +import React from 'react'; +import { render, fireEvent, screen, waitFor } from '@testing-library/react'; +import { SessionContext } from '../../../SessionContext'; // Importa el contexto necesario +import { BrowserRouter as Router } from 'react-router-dom'; +import axios from 'axios'; +import MockAdapter from 'axios-mock-adapter'; +import WiseMenStack from '../../../pages/games/WiseMenStackGame'; +import '../../../localize/i18n'; +import { expect } from 'expect-puppeteer'; + +const mockAxios = new MockAdapter(axios); + +describe('Wise Men Stack Game component', () => { + beforeEach(() => { + mockAxios.reset(); + // Mock the axios.post request to simulate a successful response + mockAxios.onGet('http://localhost:8000/questions/en/Geography').reply(200, + [{ + question: 'Which is the capital of Spain?', + options: ['Madrid', 'Barcelona', 'Paris', 'London'], + correctAnswer: 'Madrid', + categories: ['Geography'], + language: 'en' + }] + ); + + mockAxios.onPut('http://localhost:8000/statistics').reply(200, { success: true }); + mockAxios.onPut('http://localhost:8000/user/questionsRecord').reply(200, { success: true }); + + render( + + + + + + ); + }); + + it('should render configuration, question, answers and other ', async () => { + await waitFor(() => screen.getByText('GAME CONFIGURATION')); + + const button = screen.getByTestId('start-button'); + // clicks the start button to show the first question + fireEvent.click(button); + + // waits for the question to appear + await waitFor(() => screen.getByText('Which is the capital of Spain?'.toUpperCase())); + + expect(screen.findByText('Which is the capital of Spain?'.toUpperCase())); + expect(screen.findByText('Madrid')); + + }); + + it('should mark as correct right answer', async () => { + await waitFor(() => screen.getByText('GAME CONFIGURATION')); + + const button = screen.getByTestId('start-button'); + fireEvent.click(button); + + // waits for the question to appear + await waitFor(() => screen.getByText('Which is the capital of Spain?'.toUpperCase())); + + const correctAnswer = screen.getByRole('button', { name: 'Madrid' }); + + // after clicking it has changed to succeeded: + fireEvent.click(correctAnswer); + expect(screen.findByTestId("success0")); + + }); + + it('should mark as incorrect another answer', async () => { + await waitFor(() => screen.getByText('GAME CONFIGURATION')); + + const button = screen.getByTestId('start-button'); + fireEvent.click(button); + + // waits for the question to appear + await waitFor(() => screen.getByText('Which is the capital of Spain?'.toUpperCase())); + + const answers = screen.getAllByRole('button'); + const incorrectAnswer = answers[0].name === 'Madrid' ? answers[1] : answers[0]; + const id = answers[0].name === 'Madrid' ? 1 : 0; + + // now the answer is not selected: + expect(screen.findByTestId(`success${id}`)); + // after clicking it has changed to succeeded: + fireEvent.click(incorrectAnswer); + expect(screen.findByTestId(`failure${id}`)); + + }); + + + it('should only show 2 answers', async () => { await waitFor(() => screen.getByText('GAME CONFIGURATION')); + const button = screen.getByTestId('start-button'); + fireEvent.click(button); + + // waits for the question to appear + await waitFor(() => screen.getByText('Which is the capital of Spain?'.toUpperCase())); + const buttons = screen.getAllByRole('button'); + expect(buttons.length).toBe(2); + }); + + it('should not answer the question', async () => { + await waitFor(() => screen.getByText('GAME CONFIGURATION')); + + const button = screen.getByTestId('start-button'); + fireEvent.click(button); + + // waits for the question to appear + await waitFor(() => screen.getByText('Which is the capital of Spain?'.toUpperCase())); + + setTimeout(() => { + // Comprobamos que el callback ha sido llamado después del tiempo especificado + done(); // Llamamos a done para indicar que la prueba ha terminado + }, 4000); + + }, 4500); + + it('should render pause & play buttons when answered', async () => { + await waitFor(() => screen.getByText('GAME CONFIGURATION')); + const button = screen.getByTestId('start-button'); + fireEvent.click(button); + + await waitFor(() => screen.getByText('Which is the capital of Spain?'.toUpperCase())); + const correctAnswer = screen.getByRole('button', { name: 'Madrid' }); + fireEvent.click(correctAnswer); + + const pauseButton = screen.getByTestId("pause"); + expect(pauseButton); + fireEvent.click(pauseButton); + expect(screen.getByTestId("play")); + }) + + it('should render progress bar', async () => { + await waitFor(() => screen.getByText('GAME CONFIGURATION')); + const button = screen.getByTestId('start-button'); + fireEvent.click(button); + + await waitFor(() => screen.getByText('Which is the capital of Spain?'.toUpperCase())); + const progressBar = screen.getByTestId('prog_bar0'); + await expect(progressBar).toBeInTheDocument(); + }) + +}); diff --git a/webapp/src/components/NavBar.js b/webapp/src/components/NavBar.js index 79f4dc2d..1de1d56d 100644 --- a/webapp/src/components/NavBar.js +++ b/webapp/src/components/NavBar.js @@ -53,6 +53,12 @@ function NavBar() { // Add an object for each new page ]; + const logo = ( + + ) + return ( // position="static" => Barra se desplaza con scroll down @@ -98,23 +104,22 @@ function NavBar() { ))} + { logo } ):( - + <> + { logo } + )} {/* Pages list in NavBar, only displayed when menu button is not, i.e., in larger devices */} {isLoggedIn ? ( - - - + + { logo } + {pages.map((page) => ( - ))} @@ -124,12 +129,12 @@ function NavBar() { )} - + {/* Internacionalization */} - + setCategory(event.target.value)} - style={{ minWidth: '120px' }} - > - {t("Wise_Men.geography")} - {t("Wise_Men.political")} - {t("Wise_Men.sports")} - - - - - - ); -} - - // redirect to / if game over -if (shouldRedirect) { - // Redirect after 3 seconds - setTimeout(() => { - navigate('/homepage'); - }, 4000); - - - return ( - - - incorrectlyAnsweredQuestions ? 'green' : 'red', - fontSize: '4rem', // Tamaño de fuente - marginTop: '20px', // Espaciado superior - marginBottom: '50px', // Espaciado inferior - }} - > - {correctlyAnsweredQuestions > incorrectlyAnsweredQuestions ? t("Game.win_msg") : t("Game.lose_msg") } - -
- { t("Game.correct") }: {correctlyAnsweredQuestions} - { t("Game.incorrect") }: {incorrectlyAnsweredQuestions} - { t("Game.money") }: {totalScore} - { t("Game.time") }: {totalTimePlayed} -
- {showConfetti && } -
- ); -} - return ( - - - - - - {questionHistorialBar()} - - - - - {t("Game.round")} {round} - - - {questionData.question} - selectResponse(0, "FAILED")} //when time ends always fail question - > - {({ remainingTime }) => { - return ( -
-
{remainingTime}
-
- ); - }} -
-
- - - {possibleAnswers.map((option, index) => ( - - - - ))} - -
- ); -}; - -export default WiseMenStackGame; diff --git a/webapp/src/pages/DiscoveringCitiesGame.js b/webapp/src/pages/games/DiscoveringCitiesGame.js similarity index 53% rename from webapp/src/pages/DiscoveringCitiesGame.js rename to webapp/src/pages/games/DiscoveringCitiesGame.js index 7575ff67..303912b0 100644 --- a/webapp/src/pages/DiscoveringCitiesGame.js +++ b/webapp/src/pages/games/DiscoveringCitiesGame.js @@ -1,16 +1,16 @@ import * as React from 'react'; import axios from 'axios'; -import { Container, Button, CssBaseline, Grid, Typography, CircularProgress, Card, CardContent } from '@mui/material'; +import { useTheme, Container, Button, CssBaseline, Grid, Typography, CircularProgress, Card, Box, IconButton } from '@mui/material'; import { PlayArrow, Pause } from '@mui/icons-material'; import CheckIcon from '@mui/icons-material/Check'; import ClearIcon from '@mui/icons-material/Clear'; import { useNavigate } from 'react-router-dom'; -import { SessionContext } from '../SessionContext'; +import { SessionContext } from '../../SessionContext'; import { useContext } from 'react'; import Confetti from 'react-confetti'; import { CountdownCircleTimer } from "react-countdown-circle-timer"; import { useTranslation } from 'react-i18next'; -import i18n from '../localize/i18n'; +import i18n from '../../localize/i18n'; const apiEndpoint = process.env.REACT_APP_API_ENDPOINT || 'http://localhost:8000'; @@ -23,9 +23,11 @@ const DiscovertingCitiesGame = () => { //sesion information const {username} = useContext(SessionContext); - // Traductions + // Translations const { t } = useTranslation(); + const theme = useTheme(); + // state initialization const [round, setRound] = React.useState(1); const [questionData, setQuestionData] = React.useState(null); @@ -36,13 +38,16 @@ const DiscovertingCitiesGame = () => { const [correctlyAnsweredQuestions, setCorrectlyAnsweredQuestions] = React.useState(0); const [incorrectlyAnsweredQuestions, setIncorrectlyAnsweredQuestions] = React.useState(0); const [totalTimePlayed, setTotalTimePlayed] = React.useState(0); - const [timerRunning, setTimerRunning] = React.useState(true); // indicate if the timer is working - const [showConfetti, setShowConfetti] = React.useState(false); //indicates if the confetti must appear - const [questionCountdownKey, setQuestionCountdownKey] = React.useState(15); //key to update question timer - const [questionCountdownRunning, setQuestionCountdownRunning] = React.useState(false); //property to start and stop question timer + const [timerRunning, setTimerRunning] = React.useState(true); + const [showConfetti, setShowConfetti] = React.useState(false); + const [questionCountdownKey, setQuestionCountdownKey] = React.useState(15); + const [questionCountdownRunning, setQuestionCountdownRunning] = React.useState(false); const [userResponses, setUserResponses] = React.useState([]); + const [paused, setPaused] = React.useState(false); + const [passNewRound, setPassNewRound] = React.useState(false); const [language, setCurrentLanguage] = React.useState(i18n.language); + const [questionHistorial, setQuestionHistorial] = React.useState(Array(MAX_ROUNDS).fill(null)); React.useEffect(() => { @@ -74,28 +79,33 @@ const DiscovertingCitiesGame = () => { // stablish if the confetti must show or not React.useEffect(() => { - if (correctlyAnsweredQuestions > incorrectlyAnsweredQuestions) { - setShowConfetti(true); - } else { - setShowConfetti(false); + correctlyAnsweredQuestions > incorrectlyAnsweredQuestions ? setShowConfetti(true) : setShowConfetti(false); + }, [correctlyAnsweredQuestions, incorrectlyAnsweredQuestions]); + + React.useEffect(() => { + if (passNewRound && !paused) { + setRound(prevRound => { + return prevRound + 1; + }); + setButtonStates([]); } - }, [correctlyAnsweredQuestions, incorrectlyAnsweredQuestions]); - + }, [paused, passNewRound]); // gets a random question from the database and initializes button states to null const startNewRound = async () => { setAnswered(false); - // It works deploying using git repo from machine with: axios.get(`http://20.80.235.188:8000/questions`) + setPassNewRound(false); + // Updates current language setCurrentLanguage(i18n.language); axios.get(`${apiEndpoint}/questions/${language}/Cities`) .then(quest => { // every new round it gets a new question from db - setQuestionData(quest.data[0]); + setQuestionData(quest.data[0]); setButtonStates(new Array(quest.data[0].options.length).fill(null)); }).catch(error => { console.error(error); - }); + }); }; const updateStatistics = async() => { @@ -125,6 +135,7 @@ const DiscovertingCitiesGame = () => { online_incorrectly_answered_questions: 0, online_total_time_played: 0, online_games_played: 0, + online_games_won: 0 }); } catch (error) { console.error("Error:", error); @@ -197,55 +208,26 @@ const DiscovertingCitiesGame = () => { setButtonStates(newButtonStates); setTimeout(async() => { - setRound(round + 1); - setButtonStates([]); + setPassNewRound(true); setCurrentLanguage(i18n.language); - }, 4000); }; const questionHistorialBar = () => { return questionHistorial.map((isCorrect, index) => ( - - {index + 1} - + )); - }; + }; const togglePause = () => { setTimerRunning(!timerRunning); - setQuestionCountdownRunning(!timerRunning); - if (timerRunning) { - // Si el juego estaba en marcha y se pausa, deshabilitar los botones - setButtonStates(new Array(questionData.options.length).fill(true)); - } else { - // Si el juego estaba pausado y se reanuda, habilitar los botones - setButtonStates(new Array(questionData.options.length).fill(null)); - } + setPaused(!paused); } - // circular loading if (!questionData) { return ( - + @@ -253,135 +235,79 @@ const DiscovertingCitiesGame = () => { } // redirect to / if game over -if (shouldRedirect) { - // Redirect after 3 seconds - setTimeout(() => { - navigate('/homepage'); - }, 4000); + if (shouldRedirect) { + // Redirect after 4 seconds + setTimeout(() => { + navigate('/homepage'); + }, 4000); + return ( + + + incorrectlyAnsweredQuestions ? theme.palette.success.main : theme.palette.error.main }}> + {correctlyAnsweredQuestions > incorrectlyAnsweredQuestions ? t("Game.win_msg") : t("Game.lose_msg") } + + + { t("Game.correct") }: {correctlyAnsweredQuestions} + { t("Game.incorrect") }: {incorrectlyAnsweredQuestions} + { t("Game.money") }: {totalScore} + { t("Game.time") }: {totalTimePlayed} + + {showConfetti && } + + ); + } return ( - - - incorrectlyAnsweredQuestions ? 'green' : 'red', - fontSize: '4rem', // Tamaño de fuente - marginTop: '20px', // Espaciado superior - marginBottom: '50px', // Espaciado inferior - }} - > - {correctlyAnsweredQuestions > incorrectlyAnsweredQuestions ? t("Game.win_msg") : t("Game.lose_msg") } - -
- { t("Game.correct") }: {correctlyAnsweredQuestions} - { t("Game.incorrect") }: {incorrectlyAnsweredQuestions} - { t("Game.money") }: {totalScore} - { t("Game.time") }: {totalTimePlayed} -
- {showConfetti && } -
- ); -} - return ( - + - - - - - {questionHistorialBar()} - + + + { answered ? + // Pausa + togglePause()} sx={{ height: 100, width: 100, border: `2px solid ${theme.palette.primary.main}` }} + data-testid={ paused ? "play" : "pause"} > + { paused ? : } + + : + // Cronómetro + selectResponse(-1, "FAILED")}> + {({ remainingTime }) => { + return ( + + {remainingTime} + + ); + }} + + } + + + + + {questionData.question.toUpperCase()} + + + + {questionData.options.map((option, index) => ( + + + + ))} + - - {round} / {MAX_ROUNDS} - - - {questionData.question} - selectResponse(0, "FAILED")} //when time ends always fail question - > - {({ remainingTime }) => { - return ( -
-
{remainingTime}
-
- ); - }} -
-
- - - {questionData.options.map((option, index) => ( - - - - ))} - + {/* Progress Cards */} + + {questionHistorialBar()} +
); }; diff --git a/webapp/src/pages/Game.js b/webapp/src/pages/games/Game.js similarity index 73% rename from webapp/src/pages/Game.js rename to webapp/src/pages/games/Game.js index 0310eeaf..19e231f4 100644 --- a/webapp/src/pages/Game.js +++ b/webapp/src/pages/games/Game.js @@ -1,16 +1,16 @@ import * as React from 'react'; import axios from 'axios'; -import { useTheme, Container, Button, CssBaseline, Grid, Typography, CircularProgress, Card } from '@mui/material'; +import { useTheme, Container, Button, CssBaseline, Grid, Typography, CircularProgress, Card, Box, IconButton } from '@mui/material'; import { PlayArrow, Pause } from '@mui/icons-material'; import CheckIcon from '@mui/icons-material/Check'; import ClearIcon from '@mui/icons-material/Clear'; import { useNavigate } from 'react-router-dom'; -import { SessionContext } from '../SessionContext'; +import { SessionContext } from '../../SessionContext'; import { useContext } from 'react'; import Confetti from 'react-confetti'; import { CountdownCircleTimer } from "react-countdown-circle-timer"; import { useTranslation } from 'react-i18next'; -import i18n from '../localize/i18n'; +import i18n from '../../localize/i18n'; const apiEndpoint = process.env.REACT_APP_API_ENDPOINT || 'http://localhost:8000'; @@ -38,16 +38,15 @@ const Game = () => { const [correctlyAnsweredQuestions, setCorrectlyAnsweredQuestions] = React.useState(0); const [incorrectlyAnsweredQuestions, setIncorrectlyAnsweredQuestions] = React.useState(0); const [totalTimePlayed, setTotalTimePlayed] = React.useState(0); - const [timerRunning, setTimerRunning] = React.useState(true); // indicate if the timer is working - const [showConfetti, setShowConfetti] = React.useState(false); //indicates if the confetti must appear - const [questionCountdownKey, setQuestionCountdownKey] = React.useState(15); //key to update question timer - const [questionCountdownRunning, setQuestionCountdownRunning] = React.useState(false); //property to start and stop question timer + const [timerRunning, setTimerRunning] = React.useState(true); + const [showConfetti, setShowConfetti] = React.useState(false); + const [questionCountdownKey, setQuestionCountdownKey] = React.useState(15); + const [questionCountdownRunning, setQuestionCountdownRunning] = React.useState(false); const [userResponses, setUserResponses] = React.useState([]); const [paused, setPaused] = React.useState(false); const [passNewRound, setPassNewRound] = React.useState(false); const [language, setCurrentLanguage] = React.useState(i18n.language); - const [questionHistorial, setQuestionHistorial] = React.useState(Array(MAX_ROUNDS).fill(null)); React.useEffect(() => { @@ -70,7 +69,7 @@ const Game = () => { } else { setTimerRunning(false); setShouldRedirect(true); - setQuestionCountdownRunning(false); // Isnt this redundant as it is stablished when answering? + setQuestionCountdownRunning(false); updateStatistics(); updateQuestionsRecord(); } @@ -215,12 +214,7 @@ const Game = () => { const questionHistorialBar = () => { return questionHistorial.map((isCorrect, index) => ( - - + )); }; @@ -232,44 +226,24 @@ const Game = () => { // circular loading if (!questionData) { return ( - + ); } - // redirect to / if game over + // redirect to homepage if game over if (shouldRedirect) { // Redirect after 4 seconds setTimeout(() => { navigate('/homepage'); }, 4000); - return ( - + - incorrectlyAnsweredQuestions ? theme.palette.success.main : theme.palette.error.main }}> + incorrectlyAnsweredQuestions ? theme.palette.success.main : theme.palette.error.main }}> {correctlyAnsweredQuestions > incorrectlyAnsweredQuestions ? t("Game.win_msg") : t("Game.lose_msg") } @@ -284,73 +258,43 @@ const Game = () => { } return ( - + { answered ? // Pausa - + togglePause()} sx={{ height: 100, width: 100, border: `2px solid ${theme.palette.primary.main}` }} + data-testid={ paused ? "play" : "pause"} > + { paused ? : } + : // Cronómetro - selectResponse(-1, "FAILED")} //when time ends always fail question - > + selectResponse(-1, "FAILED")}> {({ remainingTime }) => { return ( -
-
{remainingTime}
-
+ + {remainingTime} + ); }}
}
- - + + {questionData.question.toUpperCase()} - + {questionData.options.map((option, index) => ( - diff --git a/webapp/src/pages/MultiplayerGame.js b/webapp/src/pages/games/MultiplayerGame.js similarity index 56% rename from webapp/src/pages/MultiplayerGame.js rename to webapp/src/pages/games/MultiplayerGame.js index 2c71b34a..2a594768 100644 --- a/webapp/src/pages/MultiplayerGame.js +++ b/webapp/src/pages/games/MultiplayerGame.js @@ -1,24 +1,21 @@ import * as React from 'react'; import axios from 'axios'; -import { Container, Button, CssBaseline, Grid, Typography, CircularProgress, Card, CardContent } from '@mui/material'; +import { useTheme, Container, Button, CssBaseline, Grid, Typography, CircularProgress, Card, Box } from '@mui/material'; import CheckIcon from '@mui/icons-material/Check'; import ClearIcon from '@mui/icons-material/Clear'; -import { SessionContext } from '../SessionContext'; +import { SessionContext } from '../../SessionContext'; import { useContext } from 'react'; import Confetti from 'react-confetti'; import { CountdownCircleTimer } from "react-countdown-circle-timer"; import { useLocation } from 'react-router-dom'; import io from 'socket.io-client'; import { useTranslation } from 'react-i18next'; -import i18n from '../localize/i18n'; - +import i18n from '../../localize/i18n'; const apiEndpoint = process.env.REACT_APP_API_ENDPOINT || 'http://localhost:8000'; const socketEndpoint = process.env.REACT_APP_MULTIPLAYER_ENDPOINT || 'ws://localhost:5010'; -const Game = () => { - const { t } = useTranslation(); - +const Game = () => { const MAX_ROUNDS = 3; const SUCCESS_SOUND_ROUTE = "/sounds/success_sound.mp3"; const FAILURE_SOUND_ROUTE = "/sounds/wrong_sound.mp3"; @@ -26,6 +23,11 @@ const Game = () => { //sesion information const {username} = useContext(SessionContext); + // Translations + const { t } = useTranslation(); + + const theme = useTheme(); + // state initialization const [round, setRound] = React.useState(1); const [questionData, setQuestionData] = React.useState(null); @@ -44,7 +46,6 @@ const Game = () => { const [userResponses, setUserResponses] = React.useState([]); const [, setCurrentLanguage] = React.useState(i18n.language); - const location = useLocation(); const { gameQuestions, roomCode} = location.state; const [socket, setSocket] = React.useState(null); @@ -95,11 +96,7 @@ const Game = () => { // stablish if the confetti must show or not React.useEffect(() => { - if (winnerPlayer === username) { - setShowConfetti(true); - } else { - setShowConfetti(false); - } + winnerPlayer === username ? setShowConfetti(true) : setShowConfetti(false); }, [winnerPlayer, username]); @@ -107,16 +104,12 @@ const Game = () => { const startNewRound = async () => { setAnswered(false); const quest = gameQuestions[round-1] - setQuestionData(quest); setButtonStates(new Array(quest.options.length).fill(null)); - }; const updateStatistics = async() => { try { - //const winner = winner === username ? 1 : 0; - await axios.put(`${apiEndpoint}/statistics`, { username:username, the_callenge_earned_money:0, @@ -223,180 +216,92 @@ const Game = () => { const questionHistorialBar = () => { return questionHistorial.map((isCorrect, index) => ( - - {index + 1} - + )); - }; + }; // circular loading if (!questionData) { return ( - + ); } -if (shouldRedirect) { - socket.emit("finished-game", username, correctlyAnsweredQuestions, totalTimePlayed); + if (shouldRedirect) { + socket.emit("finished-game", username, correctlyAnsweredQuestions, totalTimePlayed); - return ( - - - - - - {winnerPlayer === "" ? t("Multiplayer.Game.waiting_players_end") : "Game Over"} - - -
- - - - {t("Game.correct")}: {correctlyAnsweredQuestions} - - - - - {t("Game.incorrect")}: {incorrectlyAnsweredQuestions} - - - - - {t("Game.money")}: {totalScore} - - - - - {t("Game.time")}: {totalTimePlayed} - - - + return ( + + + + + {winnerPlayer === "" ? t("Multiplayer.Game.waiting_players_end") : t("Game.lose_msg")} + + + + { t("Game.correct") }: {correctlyAnsweredQuestions} + { t("Game.incorrect") }: {incorrectlyAnsweredQuestions} + { t("Game.money") }: {totalScore} + { t("Game.time") }: {totalTimePlayed} + {winnerPlayer === "" ? ( - { t("Multiplayer.Game.waiting") } + { t("Multiplayer.Game.waiting") } ) : ( - { t("Multiplayer.Game.winner_1") }: {winnerPlayer} { t("Multiplayer.Game.winner_2") } {winnerCorrect} { t("Multiplayer.Game.winner_3") } {winnerTime} { t("Multiplayer.Game.winner_4") } + + { t("Multiplayer.Game.winner_1") }: {winnerPlayer} + { t("Multiplayer.Game.winner_2") }: {winnerCorrect} { t("Multiplayer.Game.winner_3") } {winnerTime} { t("Multiplayer.Game.winner_4") } + )} -
- {showConfetti && } -
- ); -} + + {showConfetti && } +
+ ); + } + return ( - + - - + selectResponse(-1, "FAILED")}> + {({ remainingTime }) => { + return ( + + {remainingTime} + + ); }} - > - {questionHistorialBar()} - + + + + + + {questionData.question.toUpperCase()} + + + + {questionData.options.map((option, index) => ( + + + + ))} + - - {round} / {MAX_ROUNDS} - - - {questionData.question} - selectResponse(0, "FAILED")} //when time ends always fail question - > - {({ remainingTime }) => { - return ( -
-
{remainingTime}
-
- ); - }} -
-
- - - {questionData.options.map((option, index) => ( - - - - ))} - + {/* Progress Cards */} + + {questionHistorialBar()} +
); }; diff --git a/webapp/src/pages/games/MultiplayerRoom.js b/webapp/src/pages/games/MultiplayerRoom.js new file mode 100644 index 00000000..aa3e5d63 --- /dev/null +++ b/webapp/src/pages/games/MultiplayerRoom.js @@ -0,0 +1,214 @@ +import React, { useState, useEffect } from 'react'; +import { useTheme, Button, TextField, Typography, Grid, Paper, List, ListItem, CircularProgress, Container, Box } from '@mui/material'; +import io from 'socket.io-client'; +import { useContext } from 'react'; +import { SessionContext } from '../../SessionContext'; +import { useNavigate } from 'react-router-dom'; +import { useTranslation } from 'react-i18next'; +import Chat from '../../components/Chat'; + +const socketEndpoint = process.env.REACT_APP_MULTIPLAYER_ENDPOINT || 'http://localhost:5010'; + +const MultiplayerRoom = () => { + const { t } = useTranslation(); + const theme = useTheme(); + + const [roomCode, setRoomCode] = useState(""); + const [error, setError] = useState(""); + const [socket, setSocket] = useState(null); + const [writtenCode, setWrittenCode] = useState(""); + const [roomPlayers, setRoomPlayers] = useState([]); + const {username} = useContext(SessionContext); + const [gameReady, setGameReady] = useState(false); + const [roomCreator, setRoomCreator] = useState(false); + const [gameQuestions, setGameQuestions] = useState(null); + const [loadingQuestions, setLoadingQuestions] = useState(false); + const navigate = useNavigate(); + const [gameLoaded, setGameLoaded] = useState(false); + + useEffect(() => { + const newSocket = io(socketEndpoint); + setSocket(newSocket); + + newSocket.on('game-ready', (msg) => { + if(msg === "ready") { + setGameReady(true); + setLoadingQuestions(true); + } else { + setGameReady(false); + } + + }); + + newSocket.on('update-players', (roomPlayers) => { + setRoomPlayers(roomPlayers); + }); + + newSocket.on('questions-ready', (questions, roomCode) => { + setGameQuestions(questions); + setLoadingQuestions(false); + setGameLoaded(true); + }); + + newSocket.on('join-error', errorMessage => { + setError(errorMessage); + }); + + // clean at component dismount + return () => { + newSocket.disconnect(); + }; + }, [navigate]); + + useEffect(() => { + if(socket !== null) { + socket.on('btn-start-pressed', () => { + navigate('/multiplayerGame', { state: {gameQuestions, roomCode} }); + }); + } + }, [socket, navigate, gameQuestions, roomCode]) + + const handleCreateRoom = () => { + setError(""); + const code = generateRoomCode(); + setRoomCreator(true); + + socket.on('connection', () => { + + }); + + socket.emit('join-room', code, username, "create"); + }; + + const handleJoinRoom = () => { + socket.emit('join-room', writtenCode, username, "join"); + setRoomCreator(false); + setRoomCode(writtenCode); + }; + + const generateRoomCode = () => { + const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + const codeLength = 5; + + let code = ''; + for (let i = 0; i < codeLength; i++) { + const randomIndex = Math.floor(Math.random() * characters.length); + code += characters[randomIndex]; + } + setRoomCode(code); + + return code; + } + + const startGame = () => { + setGameReady(false); + socket.emit("started-game", true); + } + + return ( + + + + {t("Games.Multiplayer.name").toUpperCase()} + + + {roomCode && error === "" ? ( + <> + + { t("Multiplayer.Room.code") }: + + + {roomCode} + + + + { t("Multiplayer.Room.participants") }: + + + {roomPlayers.map((player, index) => ( + + {player} + + ))} + + + {loadingQuestions && ( +
+ + Loading questions... +
+ )} + + ) : ( + + + + { t("Multiplayer.Room.join") } + + setWrittenCode(e.target.value)} + /> + + {error && ( + + {error} + + )} + + + + + { t("Multiplayer.Room.new_game") } + + + + + )} + {roomCode && error === "" && ( + + + + )} +
+
+ ); + } + +export default MultiplayerRoom; \ No newline at end of file diff --git a/webapp/src/pages/TheChallengeGame.js b/webapp/src/pages/games/TheChallengeGame.js similarity index 50% rename from webapp/src/pages/TheChallengeGame.js rename to webapp/src/pages/games/TheChallengeGame.js index 7299b1aa..6033c9b6 100644 --- a/webapp/src/pages/TheChallengeGame.js +++ b/webapp/src/pages/games/TheChallengeGame.js @@ -1,24 +1,23 @@ import * as React from 'react'; import axios from 'axios'; - -import { Container, Button, CssBaseline, Grid, Typography, CircularProgress, useTheme, MenuItem, Select } from '@mui/material'; +import { Container, Box, Button, CssBaseline, Grid, Typography, CircularProgress, useTheme, MenuItem, Select, IconButton, Paper } from '@mui/material'; import CheckIcon from '@mui/icons-material/Check'; import ClearIcon from '@mui/icons-material/Clear'; import { useNavigate } from 'react-router-dom'; -import { SessionContext } from '../SessionContext'; +import { SessionContext } from '../../SessionContext'; import { useContext } from 'react'; import Confetti from 'react-confetti'; import { CountdownCircleTimer } from "react-countdown-circle-timer"; import Card from '@mui/material/Card'; import { useTranslation } from 'react-i18next'; -import i18n from '../localize/i18n'; +import i18n from '../../localize/i18n'; import { PlayArrow, Pause } from '@mui/icons-material'; - +import AddIcon from '@mui/icons-material/Add'; +import RemoveIcon from '@mui/icons-material/Remove'; const apiEndpoint = process.env.REACT_APP_API_ENDPOINT || 'http://localhost:8000'; - -const Game = () => { +const TheChallengeGame = () => { const navigate = useNavigate(); const SUCCESS_SOUND_ROUTE = "/sounds/success_sound.mp3"; const FAILURE_SOUND_ROUTE = "/sounds/wrong_sound.mp3"; @@ -110,7 +109,6 @@ const Game = () => { const startNewRound = async () => { setAnswered(false); setPassNewRound(false); - // It works deploying using git repo from machine with: axios.get(`http://20.80.235.188:8000/questions`) setCurrentLanguage(i18n.language); axios.get(`${apiEndpoint}/questions/${language}/${category}`) .then(quest => { @@ -231,65 +229,72 @@ const Game = () => { if (configModalOpen) { return( - - Game Configuration - -
- - - {numRounds} - -
- -
- - - {timerConfig} - -
- - {/* Dropdown for selecting category */} -
- - -
- - + + + + {t("Game.config.title")} + + + + + + {t("Game.config.num_rounds")}: + + setNumRounds(numRounds - 1)} variant="outlined" data-testId="removeRound"> + + + + {numRounds} + + setNumRounds(numRounds + 1)} variant="outlined" data-testId="addRound"> + + + + + + + {t("Game.config.time")}: + + setTimerConfig(timerConfig - 1)} variant="outlined" data-testId="removeSecond"> + + + + {timerConfig} + + setTimerConfig(timerConfig + 1)} variant="outlined" data-testId="addSecond"> + + + + + {/* Dropdown for selecting category */} + + + {t("Game.config.category")}: + + + + + + ); } const questionHistorialBar = () => { return questionHistorial.map((isCorrect, index) => ( - - + )); }; @@ -302,141 +307,90 @@ const Game = () => { // circular loading if (!questionData) { return ( - + ); } - // redirect to / if game over - if (shouldRedirect) { - // Redirect after 4 seconds - setTimeout(() => { - navigate('/homepage'); - }, 4000); + // redirect to homepage if game over + if (shouldRedirect) { + // Redirect after 4 seconds + setTimeout(() => { + navigate('/homepage'); + }, 4000); + + return ( + + + incorrectlyAnsweredQuestions ? theme.palette.success.main : theme.palette.error.main }}> + {correctlyAnsweredQuestions > incorrectlyAnsweredQuestions ? t("Game.win_msg") : t("Game.lose_msg") } + + + { t("Game.correct") }: {correctlyAnsweredQuestions} + { t("Game.incorrect") }: {incorrectlyAnsweredQuestions} + { t("Game.money") }: {totalScore} + { t("Game.time") }: {totalTimePlayed} + + {showConfetti && } + + ); + } return ( - + - incorrectlyAnsweredQuestions ? theme.palette.success.main : theme.palette.error.main }}> - {correctlyAnsweredQuestions > incorrectlyAnsweredQuestions ? t("Game.win_msg") : t("Game.lose_msg") } - - - { t("Game.correct") }: {correctlyAnsweredQuestions} - { t("Game.incorrect") }: {incorrectlyAnsweredQuestions} - { t("Game.money") }: {totalScore} - { t("Game.time") }: {totalTimePlayed} + + + { answered ? + // Pausa + togglePause()} sx={{ height: 100, width: 100, border: `2px solid ${theme.palette.primary.main}` }} + data-testid={ paused ? "play" : "pause"} > + { paused ? : } + + : + // Cronómetro + selectResponse(-1, "FAILED")}> + {({ remainingTime }) => { + return ( + + {remainingTime} + + ); + }} + + } - {showConfetti && } - - ); -} - -return ( - - - - - { answered ? - // Pausa - - : - // Cronómetro - selectResponse(-1, "FAILED")} //when time ends always fail question - > - {({ remainingTime }) => { - return ( -
-
{remainingTime}
-
- ); - }} -
- } -
- - - {questionData.question.toUpperCase()} - - - - {questionData.options.map((option, index) => ( - - - - ))} - - + + + {questionData.question.toUpperCase()} + + + + {questionData.options.map((option, index) => ( + + + + ))} + + - {/* Progress Cards */} - - {questionHistorialBar()} + {/* Progress Cards */} + + {questionHistorialBar()} + -
-); + ); }; -export default Game; \ No newline at end of file +export default TheChallengeGame; \ No newline at end of file diff --git a/webapp/src/pages/WarmQuestionGame.js b/webapp/src/pages/games/WarmQuestionGame.js similarity index 54% rename from webapp/src/pages/WarmQuestionGame.js rename to webapp/src/pages/games/WarmQuestionGame.js index bcd63cf9..cf6b4b58 100644 --- a/webapp/src/pages/WarmQuestionGame.js +++ b/webapp/src/pages/games/WarmQuestionGame.js @@ -1,23 +1,20 @@ import * as React from 'react'; import axios from 'axios'; - -import { Container, Button, CssBaseline, Grid, Typography, CircularProgress } from '@mui/material'; +import { useTheme, Container, Button, CssBaseline, Grid, Typography, CircularProgress, Card, Box, IconButton } from '@mui/material'; import { PlayArrow, Pause } from '@mui/icons-material'; import CheckIcon from '@mui/icons-material/Check'; import ClearIcon from '@mui/icons-material/Clear'; import { useNavigate } from 'react-router-dom'; -import { SessionContext } from '../SessionContext'; +import { SessionContext } from '../../SessionContext'; import { useContext } from 'react'; import Confetti from 'react-confetti'; import { CountdownCircleTimer } from "react-countdown-circle-timer"; -import Card from '@mui/material/Card'; -import CardContent from '@mui/material/CardContent'; -import i18n from '../localize/i18n'; +import { useTranslation } from 'react-i18next'; +import i18n from '../../localize/i18n'; const apiEndpoint = process.env.REACT_APP_API_ENDPOINT || 'http://localhost:8000'; - -const Game = () => { +const WarmQuestionGame = () => { const navigate = useNavigate(); const MAX_ROUNDS = 10; const SUCCESS_SOUND_ROUTE = "/sounds/success_sound.mp3"; @@ -26,6 +23,11 @@ const Game = () => { //sesion information const {username} = useContext(SessionContext); + // Translations + const { t } = useTranslation(); + + const theme = useTheme(); + // state initialization const [round, setRound] = React.useState(1); const [questionData, setQuestionData] = React.useState(null); @@ -42,11 +44,12 @@ const Game = () => { const [questionCountdownKey, setQuestionCountdownKey] = React.useState(15); //key to update question timer const [questionCountdownRunning, setQuestionCountdownRunning] = React.useState(false); //property to start and stop question timer const [userResponses, setUserResponses] = React.useState([]); + const [paused, setPaused] = React.useState(false); + const [passNewRound, setPassNewRound] = React.useState(false); const [language, setCurrentLanguage] = React.useState(i18n.language); const [questionHistorial, setQuestionHistorial] = React.useState(Array(MAX_ROUNDS).fill(null)); - React.useEffect(() => { let timer; if (timerRunning) { @@ -76,18 +79,23 @@ const Game = () => { // stablish if the confetti must show or not React.useEffect(() => { - if (correctlyAnsweredQuestions > incorrectlyAnsweredQuestions) { - setShowConfetti(true); - } else { - setShowConfetti(false); + correctlyAnsweredQuestions > incorrectlyAnsweredQuestions ? setShowConfetti(true) : setShowConfetti(false); + }, [correctlyAnsweredQuestions, incorrectlyAnsweredQuestions]); + + React.useEffect(() => { + if (passNewRound && !paused) { + setRound(prevRound => { + return prevRound + 1; + }); + setButtonStates([]); } - }, [correctlyAnsweredQuestions, incorrectlyAnsweredQuestions]); - + }, [paused, passNewRound]); // gets a random question from the database and initializes button states to null const startNewRound = async () => { setAnswered(false); - // It works deploying using git repo from machine with: axios.get(`http://20.80.235.188:8000/questions`) + setPassNewRound(false); + // Updates current language setCurrentLanguage(i18n.language); axios.get(`${apiEndpoint}/questions/${language}`) @@ -98,7 +106,6 @@ const Game = () => { }).catch(error => { console.error(error); }); - }; const updateStatistics = async() => { @@ -233,214 +240,112 @@ const Game = () => { const questionHistorialBar = () => { return questionHistorial.map((isCorrect, index) => ( - - {index + 1} - + )); - }; + }; const togglePause = () => { setTimerRunning(!timerRunning); - setQuestionCountdownRunning(!timerRunning); - if (timerRunning) { - // Si el juego estaba en marcha y se pausa, deshabilitar los botones - setButtonStates(new Array(questionData.options.length).fill(true)); - } else { - // Si el juego estaba pausado y se reanuda, habilitar los botones - setButtonStates(new Array(questionData.options.length).fill(null)); - } + setPaused(!paused); } - // circular loading if (!questionData) { return ( - + ); } - // redirect to / if game over -if (shouldRedirect) { - // Redirect after 3 seconds - setTimeout(() => { - navigate('/homepage'); - }, 4000); + // redirect to homepage if game over + if (shouldRedirect) { + // Redirect after 4 seconds + setTimeout(() => { + navigate('/homepage'); + }, 4000); + return ( + + + incorrectlyAnsweredQuestions ? theme.palette.success.main : theme.palette.error.main }}> + {correctlyAnsweredQuestions > incorrectlyAnsweredQuestions ? t("Game.win_msg") : t("Game.lose_msg") } + + + { t("Game.correct") }: {correctlyAnsweredQuestions} + { t("Game.incorrect") }: {incorrectlyAnsweredQuestions} + { t("Game.money") }: {totalScore} + { t("Game.time") }: {totalTimePlayed} + + {showConfetti && } + + ); + } return ( - - - incorrectlyAnsweredQuestions ? 'green' : 'red', - fontSize: '4rem', // Tamaño de fuente - marginTop: '20px', // Espaciado superior - marginBottom: '50px', // Espaciado inferior - }} - > - {correctlyAnsweredQuestions > incorrectlyAnsweredQuestions ? "Great Job!" : "Game Over"} - -
- Correct Answers: {correctlyAnsweredQuestions} - Incorrect Answers: {incorrectlyAnsweredQuestions} - Skipped Questions: {passedQuestions} - Total money: {totalScore} - Game time: {totalTimePlayed} seconds -
- {showConfetti && } -
- ); -} - return ( - + - - Game time: {totalTimePlayed} s - - - - - - - - {questionHistorialBar()} - + + { answered ? + // Pausa + togglePause()} sx={{ height: 100, width: 100, border: `2px solid ${theme.palette.primary.main}` }} + data-testid={ paused ? "play" : "pause"} > + { paused ? : } + + : + // Cronómetro + selectResponse(-1, "FAILED")}> + {({ remainingTime }) => { + return ( + + {remainingTime} + + ); + }} + + } + + + + + {questionData.question.toUpperCase()} + + + + {questionData.options.map((option, index) => ( + + + + ))} + - - {round} / {MAX_ROUNDS} - - - {questionData.question} - selectResponse(0, "FAILED")} //when time ends always fail question - > - {({ remainingTime }) => { - return ( -
-
{remainingTime}
-
- ); - }} -
-
- - - {questionData.options.map((option, index) => ( - - - - ))} - - - {!answered && ( - - )} + { answered ? + + : + + } - + {/* Progress Cards */} + + {questionHistorialBar()} +
); }; -export default Game; \ No newline at end of file +export default WarmQuestionGame; \ No newline at end of file diff --git a/webapp/src/pages/games/WiseMenStackGame.js b/webapp/src/pages/games/WiseMenStackGame.js new file mode 100644 index 00000000..f056c511 --- /dev/null +++ b/webapp/src/pages/games/WiseMenStackGame.js @@ -0,0 +1,360 @@ +import * as React from 'react'; +import axios from 'axios'; +import { Container, Box, Button, CssBaseline, Grid, Typography, CircularProgress, Card, Select, MenuItem, IconButton, useTheme, Paper } from '@mui/material'; +import CheckIcon from '@mui/icons-material/Check'; +import ClearIcon from '@mui/icons-material/Clear'; +import { PlayArrow, Pause } from '@mui/icons-material'; +import { useNavigate } from 'react-router-dom'; +import { SessionContext } from '../../SessionContext'; +import { useContext } from 'react'; +import Confetti from 'react-confetti'; +import { CountdownCircleTimer } from "react-countdown-circle-timer"; +import { useTranslation } from 'react-i18next'; +import i18n from '../../localize/i18n'; + +const apiEndpoint = process.env.REACT_APP_API_ENDPOINT || 'http://localhost:8000'; + +const WiseMenStackGame = () => { + const navigate = useNavigate(); + const SUCCESS_SOUND_ROUTE = "/sounds/success_sound.mp3"; + const FAILURE_SOUND_ROUTE = "/sounds/wrong_sound.mp3"; + + //sesion information + const {username} = useContext(SessionContext); + const theme = useTheme(); + + // Traductions + const { t } = useTranslation(); + + // state initialization + const [round, setRound] = React.useState(1); + const [questionData, setQuestionData] = React.useState(null); + const [buttonStates, setButtonStates] = React.useState([]); + const [answered, setAnswered] = React.useState(false); + const [shouldRedirect, setShouldRedirect] = React.useState(false); + const [totalScore, setTotalScore] = React.useState(0); + const [correctlyAnsweredQuestions, setCorrectlyAnsweredQuestions] = React.useState(0); + const [incorrectlyAnsweredQuestions, setIncorrectlyAnsweredQuestions] = React.useState(0); + const [totalTimePlayed, setTotalTimePlayed] = React.useState(0); + const [timerRunning, setTimerRunning] = React.useState(true); // indicate if the timer is working + const [showConfetti, setShowConfetti] = React.useState(false); //indicates if the confetti must appear + const [questionCountdownKey, ] = React.useState(60); //key to update question timer + const [questionCountdownRunning, setQuestionCountdownRunning] = React.useState(false); //property to start and stop question timer + const [userResponses, setUserResponses] = React.useState([]); + const [language, setCurrentLanguage] = React.useState(i18n.language); + + const [category, setCategory] = React.useState('Geography'); + const [possibleAnswers, setPossibleAnswers] = React.useState([]); + const [isConfigured, setConfiguration] = React.useState(false); + const [paused, setPaused] = React.useState(false); + const [passNewRound, setPassNewRound] = React.useState(false); + + const [questionHistorial, setQuestionHistorial] = React.useState(Array(round).fill(null)); + + React.useEffect(() => { + let timer; + if (timerRunning) { + timer = setInterval(() => { + setTotalTimePlayed((prevTotalTime) => prevTotalTime + 1); + }, 1000); + } + + return () => clearInterval(timer); + }, [timerRunning]); + + // hook to initiating new rounds if the current number of rounds is less than or equal to 3 + React.useEffect(() => { + if (totalTimePlayed <= questionCountdownKey) { + startNewRound(); + setQuestionCountdownRunning(true); + } else { + setTimerRunning(false); + setShouldRedirect(true); + setQuestionCountdownRunning(false); + updateStatistics(); + updateQuestionsRecord(); + } + // eslint-disable-next-line + }, [round]); + + // stablish if the confetti must show or not + React.useEffect(() => { + if (correctlyAnsweredQuestions > incorrectlyAnsweredQuestions) { + setShowConfetti(true); + } else { + setShowConfetti(false); + } + }, [correctlyAnsweredQuestions, incorrectlyAnsweredQuestions]); + + React.useEffect(() => { + if (passNewRound && !paused) { + setRound(prevRound => { + return prevRound + 1; + }); + setButtonStates([]); + } + }, [paused, passNewRound]); + + const startNewRound = async () => { + setAnswered(false); + + setPassNewRound(false); + + // Updates current language + setCurrentLanguage(i18n.language); + axios.get(`${apiEndpoint}/questions/${language}/${category}`) + .then(quest => { + // every new round it gets a new question from db + setQuestionData(quest.data[0]); + setButtonStates(new Array(2).fill(null)); + getPossibleOptions(quest.data[0]); + + }).catch(error => { + console.error("Could not get questions", error); + }); + + }; + + // It puts 2 possible answers into an array making sure that the correct answer is not repeated + const getPossibleOptions = async (question) => { + var options = []; + options.push(question.correctAnswer); + let randomNumber ; + do { + randomNumber = Math.floor(Math.random() * question.options.length); + } while (question.options[randomNumber] === question.correctAnswer); + options.push(question.options[randomNumber]); + options = shuffleArray(options); + setPossibleAnswers(options); + } + + // Shuffles array + function shuffleArray(array) { + const random = Math.random(); + const randomFactor = random < 0.5 ? -1 : 1; + return array.sort(() => randomFactor); + } + + const updateStatistics = async() => { + try { + await axios.put(`${apiEndpoint}/statistics`, { + username:username, + wise_men_stack_earned_money:totalScore, + wise_men_stack_correctly_answered_questions:correctlyAnsweredQuestions, + wise_men_stack_incorrectly_answered_questions:incorrectlyAnsweredQuestions, + wise_men_stack_games_played:1 + }); + } catch (error) { + console.error("Error:", error); + }; + } + + const updateQuestionsRecord = async() => { + try { + await axios.put(`${apiEndpoint}/questionsRecord`, { + questions: userResponses, + username: username, + gameMode: "WiseMenStack" + }); + } catch (error) { + console.error("Error:", error); + }; + } + + // this function is called when a user selects a response. + const selectResponse = async (index, response) => { + setAnswered(true); + const newButtonStates = [...buttonStates]; + + //setQuestionCountdownRunning(false); + + //check answer + if (response === questionData.correctAnswer) { + const userResponse = { + question: questionData.question, + response: response, + options: questionData.options, + correctAnswer: questionData.correctAnswer + }; + setUserResponses(prevResponses => [...prevResponses, userResponse]); + + newButtonStates[index] = "success" + const sucessSound = new Audio(SUCCESS_SOUND_ROUTE); + sucessSound.volume = 0.40; + sucessSound.play(); + setCorrectlyAnsweredQuestions(correctlyAnsweredQuestions + 1); + setTotalScore(totalScore + 20); + + const newQuestionHistorial = [...questionHistorial]; + newQuestionHistorial[round-1] = true; + setQuestionHistorial(newQuestionHistorial); + } else { + const userResponse = { + question: questionData.question, + response: response, + options: questionData.options, + correctAnswer: questionData.correctAnswer + }; + setUserResponses(prevResponses => [...prevResponses, userResponse]); + newButtonStates[index] = "failure"; + const failureSound = new Audio(FAILURE_SOUND_ROUTE); + failureSound.volume = 0.40; + failureSound.play(); + for (let i = 0; i < questionData.options.length; i++) { + if (questionData.options[i] === questionData.correctAnswer) { + newButtonStates[i] = "success"; + } + } + setIncorrectlyAnsweredQuestions(incorrectlyAnsweredQuestions + 1); + + const newQuestionHistorial = [...questionHistorial]; + newQuestionHistorial[round-1] = false; + setQuestionHistorial(newQuestionHistorial); + } + + setButtonStates(newButtonStates); + + setTimeout(async() => { + setPassNewRound(true); + setCurrentLanguage(i18n.language); + }, 4000); + }; + + const questionHistorialBar = () => { + return questionHistorial.map((isCorrect, index) => ( + + )); + }; + + const togglePause = () => { + setTimerRunning(!timerRunning); + setPaused(!paused); + } + + if(!isConfigured) { + return ( + + + + {t("Game.config.title")} + + + + + {t("Wise_Men.instructions1")} + {t("Wise_Men.instructions2")} + {t("Wise_Men.instructions3")} + + + {/* Dropdown for selecting category */} + + + {t("Game.config.category")}: + + + + + + + + + ); + } + + // circular loading + if (!questionData) { + return ( + + + + + ); + } + + // redirect to homepage if game over + if (shouldRedirect) { + // Redirect after 4 seconds + setTimeout(() => { + navigate('/homepage'); + }, 4000); + + return ( + + + incorrectlyAnsweredQuestions ? theme.palette.success.main : theme.palette.error.main }}> + {correctlyAnsweredQuestions > incorrectlyAnsweredQuestions ? t("Game.win_msg") : t("Game.lose_msg") } + + + { t("Game.correct") }: {correctlyAnsweredQuestions} + { t("Game.incorrect") }: {incorrectlyAnsweredQuestions} + { t("Game.money") }: {totalScore} + { t("Game.time") }: {totalTimePlayed} + + {showConfetti && } + + ); + } + + return ( + + + + + { answered ? + // Pausa + togglePause()} sx={{ height: 100, width: 100, border: `2px solid ${theme.palette.primary.main}` }} + data-testid={ paused ? "play" : "pause"} > + { paused ? : } + + : + // Cronómetro + selectResponse(-1, "FAILED")}> + {({ remainingTime }) => { + return ( + + {remainingTime} + + ); + }} + + } + + + + + {questionData.question.toUpperCase()} + + + + {possibleAnswers.map((option, index) => ( + + + + ))} + + + + + {questionHistorialBar()} + { answered || round === 1 ? : } + + + ); +}; + +export default WiseMenStackGame; \ No newline at end of file