diff --git a/docs/src/13_testability.adoc b/docs/src/13_testability.adoc index 1907bfd4..7d61329a 100644 --- a/docs/src/13_testability.adoc +++ b/docs/src/13_testability.adoc @@ -1,6 +1,6 @@ ifndef::imagesdir[:imagesdir: ../images] -[[section-technical-risks]] +[[section-testability]] == Testability Testability is a crucial aspect of software development. It refers to the ease with which a software system can be tested to ensure its correctness and reliability. By writing tests, developers can verify that their code behaves as expected and identify any potential issues or bugs. There are various types of tests that can be performed, such as unit tests, integration tests, and end-to-end tests. Unit tests focus on testing individual components or functions in isolation, while integration tests verify the interaction between different components. End-to-end tests simulate real-world scenarios to ensure the entire system functions correctly. diff --git a/webapp/src/components/Game.js b/webapp/src/components/Game.js index 9c3a1d27..cc705895 100644 --- a/webapp/src/components/Game.js +++ b/webapp/src/components/Game.js @@ -200,10 +200,8 @@ const Game = () => { button.onmouse = colorOnMousePreguntas; }); - incrementQuestion(); - - - + setQuestionCounter(qc => qc + 1); + }catch (error){ console.error('Error:', error); @@ -227,7 +225,7 @@ const Game = () => { const correctButton = document.getElementById(buttonId); if (correctButton) { correctButton.style.backgroundColor = "rgba(79, 141, 18, 0.726)"; - incrementCorrect(); + setCorrectCounter(correct => correct + 1); } }else{ const buttonId = `button_${index}`; @@ -244,7 +242,7 @@ const Game = () => { } } - incrementIncorrect(); + setIncorrectCounter(incorrect => incorrect + 1); } const buttons = document.querySelectorAll('button[title="btnsPreg"]'); @@ -253,7 +251,7 @@ const Game = () => { button.onmouse = null; }); - decrementQuestionsToAnswer(); + setQuestionsToAnswer(toAnswer => toAnswer - 1); if (!isGameFinished()) { setTimeout(() => { @@ -314,14 +312,6 @@ const getQuestions = () => { correctAnswers: correctCounter, incorrectAnswers: numberOfQuestions-correctCounter }; - //console.log("Se va a guardar la siguiente partida:"); - //console.log("Username:", newGame.username); - //console.log("Duración:", newGame.duration); - //console.log("Preguntas:", newGame.questions); - //console.log("Porcentaje de Aciertos:", newGame.percentage); - //console.log("Número Total de Preguntas:", newGame.totalQuestions); - //console.log("Número de Respuestas Correctas:", newGame.correctAnswers); - //console.log("Número de Respuestas Incorrectas:", newGame.incorrectAnswers); setGameData(newGame); @@ -334,24 +324,6 @@ const getQuestions = () => { }); } - const incrementCorrect = () => { - setCorrectCounter(correct => correct + 1); - }; - - const incrementIncorrect = () => { - setIncorrectCounter(incorrect => incorrect + 1); - } - - const decrementQuestionsToAnswer = () => { - setQuestionsToAnswer(toAnswer => toAnswer - 1); - } - - const incrementQuestion = () => { - setQuestionCounter(qc => qc + 1); - } - - - useEffect(() => { if (isTimedOut && !isFinished) { // mostrar la respuesta correcta @@ -364,8 +336,8 @@ const getQuestions = () => { } } - incrementIncorrect(); - decrementQuestionsToAnswer(); + setIncorrectCounter(incorrect => incorrect + 1); + setQuestionsToAnswer(toAnswer => toAnswer - 1); setTimeout(() => { if (!isGameFinished()) { @@ -417,7 +389,7 @@ const getQuestions = () => { -
+
{time}
diff --git a/webapp/src/components/Game.test.js b/webapp/src/components/Game.test.js index 72c0a533..6ca6cfce 100644 --- a/webapp/src/components/Game.test.js +++ b/webapp/src/components/Game.test.js @@ -1,5 +1,5 @@ import React from 'react'; -import { render, fireEvent, screen, waitFor } from '@testing-library/react'; +import { render, fireEvent, screen, act, waitFor } from '@testing-library/react'; import axios from 'axios'; import MockAdapter from 'axios-mock-adapter'; import Game from './Game'; @@ -7,14 +7,16 @@ import { MemoryRouter } from 'react-router-dom'; // Importa MemoryRouter const mockAxios = new MockAdapter(axios); -const renderGameComponent = () => { +const renderGameComponent = (numQuestions, timePerQuestion) => { render( - + ); }; + + const mockQuestionData = { responseQuestionObject: 'What is 2 + 2?', responseCorrectOption: '4', @@ -22,16 +24,56 @@ const mockQuestionData = { }; describe('Game component', () => { + let consoleErrorSpy; + + beforeEach(() => { mockAxios.reset(); jest.useFakeTimers(); jest.clearAllMocks(); localStorage.clear(); + consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => { }); + }); + + + afterEach(() => { + // Restaurar console.error después de cada test + consoleErrorSpy.mockRestore(); + }); + + it('should handle API error correctly', async () => { + renderGameComponent(); + + mockAxios.onGet('http://localhost:8000/createquestion').reply(500); // Simular error de servidor + + await waitFor(() => { + // Verificar que console.error fue llamado con el mensaje de error esperado + expect(consoleErrorSpy).toHaveBeenCalledWith('Error:', expect.anything()); + }); + }); + + + it('muestra la página principal correctamente', async () => { + renderGameComponent(); + + // Open the confirmation dialog + fireEvent.click(screen.getByText('Volver al menú principal')); + + // Simulate clicking the confirm button + fireEvent.click(screen.getByText('Confirmar')); + + // Wait for the component to unmount (indicating the redirection) + await waitFor(() => { + expect(screen.queryByText('Game')).not.toBeInTheDocument(); + + }); + }); + it('should display initial elements correctly', () => { renderGameComponent(); - + // Verificar que elementos iniciales se muestren correctamente expect(screen.getByText('Saber y Ganar Juego')).toBeInTheDocument(); expect(screen.getByText('Preguntas restantes: 5')).toBeInTheDocument(); // Depende de la configuración del juego @@ -43,9 +85,9 @@ describe('Game component', () => { it('should display question and answer options', async () => { renderGameComponent(); - + mockAxios.onGet('http://localhost:8000/createquestion').reply(200, { data: mockQuestionData }); - + await waitFor(() => { // Esperar a que el texto de la pregunta cambie a "Pregunta 1" if (screen.queryByText(/Pregunta 1:/i)) { @@ -62,9 +104,9 @@ describe('Game component', () => { it('should handle correct answer click', async () => { renderGameComponent(); - + mockAxios.onGet('http://localhost:8000/createquestion').reply(200, { data: mockQuestionData }); - + // Esperar hasta que se muestre "Pregunta 1" await waitFor(() => { if (screen.queryByText(/Pregunta 1:/i)) { @@ -77,12 +119,12 @@ describe('Game component', () => { } }); }); - + it('should handle incorrect answer click', async () => { renderGameComponent(); - + mockAxios.onGet('http://localhost:8000/createquestion').reply(200, { data: mockQuestionData }); - + // Esperar hasta que se muestre "Pregunta 1" await waitFor(() => { if (screen.queryByText(/Pregunta 1:/i)) { @@ -112,26 +154,27 @@ describe('Game component', () => { it('should redirect to main page after confirmation', async () => { renderGameComponent(); - + // Open the confirmation dialog fireEvent.click(screen.getByText('Volver al menú principal')); - + // Simulate clicking the confirm button fireEvent.click(screen.getByText('Confirmar')); - + // Wait for the component to unmount (indicating the redirection) await waitFor(() => { expect(screen.queryByText('Game')).not.toBeInTheDocument(); + }); }); - + it('should display end game message and enable "Back to Main Menu" button when game is finished', async () => { renderGameComponent(); - + for (let i = 1; i <= 5; i++) { mockAxios.onGet('http://localhost:8000/createquestion').reply(200, { data: mockQuestionData }); - + await waitFor(() => { if (screen.queryByText(`Pregunta ${i}:`)) { fireEvent.click(screen.getByText('4')); @@ -143,17 +186,17 @@ describe('Game component', () => { } // Verificar que la partida haya finalizado correctamente - await waitFor(() => { - if (screen.queryByText(/Partida finalizada/i)) { - expect(screen.getByText(/Partida finalizada/i)).toBeInTheDocument(); - expect(screen.getByText(/Gracias por jugar/i)).toBeInTheDocument(); - expect(screen.getByText(/Puntuación/i)).toBeInTheDocument(); - expect(screen.getByText(/Volver al menú principal/i)).toBeInTheDocument(); - } - }); + await waitFor(() => { + if (screen.queryByText(/Partida finalizada/i)) { + expect(screen.getByText(/Partida finalizada/i)).toBeInTheDocument(); + expect(screen.getByText(/Gracias por jugar/i)).toBeInTheDocument(); + expect(screen.getByText(/Puntuación/i)).toBeInTheDocument(); + expect(screen.getByText(/Volver al menú principal/i)).toBeInTheDocument(); + } + }); + + - - }); @@ -161,14 +204,14 @@ describe('Game component', () => { it('should disable buttons when time runs out', async () => { renderGameComponent(); - + mockAxios.onGet('http://localhost:8000/createquestion').reply(200, { data: mockQuestionData }); - + // Esperar hasta que se muestre "Pregunta 1" await waitFor(() => { if (screen.queryByText(/Pregunta 1:/i)) { jest.advanceTimersByTime(10000); // Avanzar el temporizador por 10 segundos - + // Verificar que los botones estén deshabilitados const buttons = screen.getAllByRole('button', { name: /answer/i }); buttons.forEach(button => { @@ -177,32 +220,56 @@ describe('Game component', () => { } }); }); - + it('should handle timeout correctly', async () => { // Define una función simulada para handleShowQuestion const handleShowQuestion = jest.fn(); - + renderGameComponent({ handleShowQuestion }); // Pasa la función simulada como propiedad - + await waitFor(() => { mockAxios.onGet('http://localhost:8000/createquestion').reply(200, { data: mockQuestionData }); }); - + await waitFor(() => { if (screen.queryByText(/Pregunta 1:/i)) { - jest.advanceTimersByTime(10000); + jest.advanceTimersByTime(10000); expect(screen.getByText(correctOption)).toHaveStyle({ backgroundColor: 'rgba(79, 141, 18, 0.726)' }); expect(incrementIncorrect).toHaveBeenCalledTimes(1); expect(decrementQuestionsToAnswer).toHaveBeenCalledTimes(1); } }); - + await waitFor(() => { expect(handleShowQuestion).toHaveBeenCalledTimes(0); // Verifica que handleShowQuestion se llame una vez }); }); - + + + + it('should decrement questions to answer when question is answered', async () => { + renderGameComponent(5, 10); // Configuración inicial con 5 preguntas y 10 segundos por pregunta + + mockAxios.onGet('http://localhost:8000/createquestion').reply(200, { data: mockQuestionData }); + + // Iterar sobre las preguntas y respuestas + for (let i = 1; i <= 5; i++) { + // Esperar hasta que se muestre la pregunta i + await waitFor(() => { + if (screen.queryByText(`Pregunta ${i}:`)) { + // Una vez que se muestre la pregunta, hacer clic en una respuesta + fireEvent.click(screen.getByText('4')); + + // Verificar que el número de preguntas restantes se haya decrementado correctamente + expect(screen.getByText(`Preguntas restantes: ${5 - i}`)).toBeInTheDocument(); + } + }); + } + }); + + + it('should handle dialog open and close', async () => { renderGameComponent(); @@ -259,12 +326,12 @@ describe('Game component', () => { } await waitFor(() => { - if (screen.queryByText('Partida finalizada')) { - expect(screen.getByText('Partida finalizada')).toBeInTheDocument(); - expect(screen.getByText('Gracias por jugar')).toBeInTheDocument(); - expect(screen.getByText('Puntuación')).toBeInTheDocument(); - expect(screen.getByText('Volver al menú principal')).toBeInTheDocument(); - } + if (screen.queryByText('Partida finalizada')) { + expect(screen.getByText('Partida finalizada')).toBeInTheDocument(); + expect(screen.getByText('Gracias por jugar')).toBeInTheDocument(); + expect(screen.getByText('Puntuación')).toBeInTheDocument(); + expect(screen.getByText('Volver al menú principal')).toBeInTheDocument(); + } }); }); @@ -303,14 +370,14 @@ describe('Game component', () => { it('should display the timer correctly', async () => { renderGameComponent(); - + expect(screen.getByText('10')).toBeInTheDocument(); // Comprobar el valor inicial del temporizador - + // Esperar a que el temporizador llegue a cero await waitFor(() => { - if (screen.queryByText('0')) { - expect(screen.queryByText('0')).toBeInTheDocument(); // Comprobar que el temporizador llega a cero - } + if (screen.queryByText('0')) { + expect(screen.queryByText('0')).toBeInTheDocument(); // Comprobar que el temporizador llega a cero + } }); }); @@ -320,16 +387,38 @@ describe('Game component', () => { // Verificar que el temporizador comience con el valor correcto expect(screen.getByText('10')).toBeInTheDocument(); - for(let i = 9; i >= 0; i--){ - await waitFor(() => { - if (screen.queryByText(String(i))) { - expect(screen.queryByText(String(i))).toBeInTheDocument(); - } - }); + for (let i = 9; i >= 0; i--) { + await waitFor(() => { + if (screen.queryByText(String(i))) { + expect(screen.queryByText(String(i))).toBeInTheDocument(); + } + }); } // Verificar que el temporizador no sea negativo expect(screen.queryByText('-1')).not.toBeInTheDocument(); -}); + }); + + + + it('should display loading spinner while fetching questions', async () => { + renderGameComponent(); + + + await waitFor(() => { + // Verifica si se muestra el spinner de carga al principio + expect(screen.getByTestId('Timer')).toBeInTheDocument(); + }); + + + + // Simula una respuesta exitosa del servidor después de un breve retraso + await waitFor(() => { + mockAxios.onGet('http://localhost:8000/createquestion').reply(200, { data: mockQuestionData }); + }); + }); + + + }); diff --git a/webapp/src/components/HistoricalData.test.js b/webapp/src/components/HistoricalData.test.js index 68b4b099..f5f22d27 100644 --- a/webapp/src/components/HistoricalData.test.js +++ b/webapp/src/components/HistoricalData.test.js @@ -7,14 +7,30 @@ import { BrowserRouter as Router } from 'react-router-dom'; const mockAxios = new MockAdapter(axios); describe('HistoricalData component', () => { + let consoleErrorSpy; + + beforeEach(() => { + // Crear un spy para console.error antes de cada test + consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); + }); + afterEach(() => { + // Restaurar console.error después de cada test + consoleErrorSpy.mockRestore(); mockAxios.reset(); }); it('muestra la página con el histórico de preguntas generadas', async () => { const question1 = ['¿Cual es la capital de Venezuela?', 'Caracas', 'Doha', 'Barcelona', 'Nasáu']; const question2 = ['¿Cual es la capital de Francia?', 'París', 'Londres', 'Madrid', 'Roma']; - const mockUsers = [question1, question2]; + const question3 = ['¿Cuál es la capital de Argentina?', 'Buenos Aires', 'Santiago', 'Lima', 'Bogotá']; + const question4 = ['¿Cuál es la capital de Japón?', 'Tokio', 'Pekín', 'Bangkok', 'Seúl']; + const question5 = ['¿Cuál es la capital de Italia?', 'Gijón', 'Atenas', 'Moscú', 'Oviedo']; + const question6 = ['¿Cuál es la capital de Australia?', 'Canberra', 'Wellington', 'Sydney', 'Melbourne']; + const question7 = ['¿Cuál es la capital de Sudáfrica?', 'Pretoria', 'El Cairo', 'Nairobi', 'Abiyán']; + + const mockUsers = [question1, question2, question3, question4, question5, question6, question7]; + mockAxios.onGet("http://localhost:8000/getquestionshistory").reply(200, mockUsers); render( @@ -23,18 +39,52 @@ describe('HistoricalData component', () => { await waitFor(() => { expect(screen.getByText('¿Cual es la capital de Venezuela?')).toBeInTheDocument(); - expect(screen.getByText('Caracas')).toBeInTheDocument(); - expect(screen.getByText('Doha')).toBeInTheDocument(); - expect(screen.getByText('Barcelona')).toBeInTheDocument(); - expect(screen.getByText('Nasáu')).toBeInTheDocument(); + expect(screen.queryByText('Caracas')).toBeInTheDocument(); + expect(screen.queryByText('Doha')).toBeInTheDocument(); + expect(screen.queryByText('Barcelona')).toBeInTheDocument(); + expect(screen.queryByText('Nasáu')).toBeInTheDocument(); expect(screen.getByText('¿Cual es la capital de Francia?')).toBeInTheDocument(); - expect(screen.getByText('París')).toBeInTheDocument(); - expect(screen.getByText('Londres')).toBeInTheDocument(); - expect(screen.getByText('Madrid')).toBeInTheDocument(); - expect(screen.getByText('Roma')).toBeInTheDocument(); + expect(screen.queryByText('París')).toBeInTheDocument(); + expect(screen.queryByText('Londres')).toBeInTheDocument(); + expect(screen.queryByText('Madrid')).toBeInTheDocument(); + expect(screen.queryByText('Roma')).toBeInTheDocument(); + }); + + expect(screen.getByText('Rows per page:')).toBeInTheDocument(); + + const rowsPerPageSelect = screen.getByRole('combobox', { name: /Rows per page:/ }); + + // Verifica que esté presente el elemento de selección de filas por página + expect(rowsPerPageSelect).toBeInTheDocument(); + + await waitFor(() => { + // Simula hacer clic en el ícono de la página siguiente + const nextPageIcon = screen.getByTestId('KeyboardArrowRightIcon'); + fireEvent.click(nextPageIcon); + }); + + + }); + + + + it('maneja correctamente el error al obtener el historial de preguntas', async () => { + // Mock de la respuesta de error del servidor + mockAxios.onGet("http://localhost:8000/getquestionshistory").reply(500, { error: 'Internal Server Error' }); - expect(screen.getByText('Rows per page:')).toBeInTheDocument(); + render( + + ); + + // Esperar a que se maneje el error + await waitFor(() => { + // Verificar que console.error fue llamado con el mensaje de error esperado + expect(consoleErrorSpy).toHaveBeenCalledWith('Error:', expect.anything()); }); + + }); + + });