diff --git a/webapp/src/pages/Social/GroupDetails.js b/webapp/src/pages/Social/GroupDetails.js
new file mode 100644
index 00000000..1bbb1cdd
--- /dev/null
+++ b/webapp/src/pages/Social/GroupDetails.js
@@ -0,0 +1,157 @@
+import React, { useState, useEffect } from "react";
+import { useParams } from "react-router-dom";
+import {
+ Container,
+ Box,
+ Text,
+ Heading,
+ Table,
+ Thead,
+ Tbody,
+ Tr,
+ Th,
+ Td,
+ Avatar,
+ Link,
+ Button
+} from "@chakra-ui/react";
+import Nav from "../../components/Nav/Nav.js";
+import Footer from "../../components/Footer/Footer.js";
+import { useTranslation } from "react-i18next";
+import Perfil from "../../components/Profile/Profile.js";
+import { useNavigate } from "react-router-dom";
+
+
+const GroupDetails = () => {
+ const { t } = useTranslation();
+ const [group, setGroup] = useState(null);
+ const [error, setError] = useState(null);
+ const [user, setUser] = useState(null);
+ const { groupName } = useParams();
+ const apiEndpoint =
+ process.env.REACT_APP_API_ENDPOINT || "http://localhost:8000";
+
+ const navigate = useNavigate();
+
+ useEffect(() => {
+ fetchGroupDetails();
+ // eslint-disable-next-line
+ }, []);
+
+ const fetchGroupDetails = async () => {
+ try {
+ const response = await fetch(
+ `${apiEndpoint}/group/${encodeURIComponent(groupName)}`
+ );
+ if (!response.status === 200) {
+ throw new Error("Network response was not ok");
+ }
+ const data = await response.json();
+ setGroup(data.group);
+ setError(null);
+ } catch (error) {
+ setError(error);
+ console.error("Error al obtener los detalles del grupo:", error);
+ }
+ };
+
+ const redirectToProfile = (member) => {
+ navigate(`/perfil/${member.username}`);
+ };
+
+
+ if(user){
+ return (
+ <>
+
+
+
+
+ >
+ );
+ }
+
+ if (error) {
+ return (
+ <>
+
+
+ Error: {error.message}
+
+
+ >
+ );
+ }
+
+ if (!group) {
+ return (
+ <>
+
+
+ Cargando...
+
+
+ >
+ );
+ }
+
+ return (
+ group && group.members && (
+ <>
+
+
+
+ {t("pages.groupdetails.details")} {group.name}
+
+
+
+ {t("pages.groupdetails.createdBy")}{" "}
+ {group.members.length > 0 ? group.members[0] : ""}
+ {" " + t("pages.groupdetails.when")}{" "}
+ {new Date(group.createdAt).toLocaleDateString()}
+
+
+
+ {t("pages.groupdetails.participants")} ({group.members.length}) :
+
+
+
+
+ {t("pages.groupdetails.avatar")} |
+ {t("pages.groupdetails.name")} |
+ {t("pages.groupdetails.viewProfile")} |
+
+
+
+ {group.members.map((member, index) => (
+
+
+
+ |
+ {member} |
+
+ redirectToProfile(member)}
+ >
+ {t("pages.groupdetails.viewProfile")}
+
+ |
+
+ ))}
+
+
+
+
+
+ >
+ )
+ );
+};
+
+export default GroupDetails;
diff --git a/webapp/src/pages/Social/GroupDetails.test.js b/webapp/src/pages/Social/GroupDetails.test.js
new file mode 100644
index 00000000..171aa3b1
--- /dev/null
+++ b/webapp/src/pages/Social/GroupDetails.test.js
@@ -0,0 +1,106 @@
+import React from 'react';
+import { render, screen, waitFor, fireEvent } from "@testing-library/react";
+import { MemoryRouter } from 'react-router-dom';
+import GroupDetails from './GroupDetails';
+import { I18nextProvider } from "react-i18next";
+import i18n from "../../i18n.js";
+
+const renderComponentWithRouter = async () => {
+ render(
+
+
+
+
+
+ );
+ localStorage.setItem("username", "user1");
+ };
+
+let originalFetch;
+
+beforeEach(() => {
+ originalFetch = global.fetch;
+ global.fetch = jest.fn();
+});
+
+afterEach(() => {
+ global.fetch = originalFetch;
+ jest.restoreAllMocks();
+});
+
+const groupData = {
+ group : {
+ name: 'exampleGroup',
+ members: ['user1', 'user2'],
+ createdAt: '2024-04-11T12:00:00Z',
+ }
+ };
+
+const mockNavigate = jest.fn();
+ jest.mock('react-router-dom', () => ({
+ ...jest.requireActual('react-router-dom'),
+ useNavigate: () => mockNavigate,
+ }));
+
+const checks = (async () => {
+ await waitFor(() => {
+ expect(screen.getByText('Detalles del grupo exampleGroup')).toBeInTheDocument();
+ expect(screen.getByText('Avatar')).toBeInTheDocument();
+ expect(screen.getByText('Nombre')).toBeInTheDocument();
+ const viewProfile = screen.getAllByText('Ver perfil');
+ expect(viewProfile).toHaveLength(3);
+ expect(screen.getByTestId('user-avatar-user1')).toBeInTheDocument();
+ expect(screen.getByTestId('user-avatar-user2')).toBeInTheDocument();
+ expect(screen.getByText('user1')).toBeInTheDocument();
+ expect(screen.getByText('user2')).toBeInTheDocument();
+ });
+});
+
+describe('GroupDetails', () => {
+ beforeEach(() => {
+ localStorage.clear();
+ });
+
+ afterEach(() => {
+ global.fetch.mockRestore();
+ });
+
+
+ it('renders loading text when group data is not yet fetched', () => {
+ jest.spyOn(global, "fetch").mockResolvedValue({
+ json: jest.fn().mockResolvedValueOnce(groupData),
+ });
+ renderComponentWithRouter();
+ expect(screen.getByText('Cargando...')).toBeInTheDocument();
+ });
+
+ it('renders group details when data is fetched', async () => {
+
+ jest.spyOn(global, "fetch").mockResolvedValue({
+ status: 200,
+ json: jest.fn().mockResolvedValueOnce(groupData),
+ });
+
+ renderComponentWithRouter();
+
+ await checks();
+ });
+
+ it('redirects to user profile when view profile link is clicked', async () => {
+
+ jest.spyOn(global, "fetch").mockResolvedValue({
+ status: 200,
+ json: jest.fn().mockResolvedValueOnce(groupData),
+ });
+
+ renderComponentWithRouter();
+
+ await checks();
+
+ const viewProfileButtons = screen.getByTestId('view-profile-button-user1');
+ console.log(viewProfileButtons);
+
+ fireEvent.click(viewProfileButtons);
+ });
+});
+
diff --git a/webapp/src/pages/Social/Groups.js b/webapp/src/pages/Social/Groups.js
new file mode 100644
index 00000000..ebad1366
--- /dev/null
+++ b/webapp/src/pages/Social/Groups.js
@@ -0,0 +1,178 @@
+import React, { useState, useEffect } from "react";
+import {
+ Box,
+ Container,
+ Text,
+ Button,
+ Input,
+ InputGroup,
+ Alert,
+ Table,
+ Thead,
+ Tbody,
+ Tr,
+ Th,
+ Td,
+ Flex,
+} from "@chakra-ui/react";
+import Nav from "../../components/Nav/Nav.js";
+import Footer from "../../components/Footer/Footer.js";
+import { useTranslation } from "react-i18next";
+
+const Groups = () => {
+ const { t } = useTranslation();
+ const [groups, setGroups] = useState([]);
+ const [name, setName] = useState("");
+ const [error, setError] = useState("");
+ const [alertMessage, setAlertMessage] = useState("");
+ const username = localStorage.getItem("username");
+
+ const apiEndpoint =
+ process.env.REACT_APP_API_ENDPOINT || "http://localhost:8000";
+
+ useEffect(() => {
+ fetchData();
+ // eslint-disable-next-line
+ }, []);
+
+ const fetchData = async () => {
+ fetch(`${apiEndpoint}/group/list`)
+ .then((response) => response.json())
+ .then((data) => {
+ const userGroups = data.groups.filter(
+ (group) => !group.members.includes(username)
+ );
+ setGroups(userGroups);
+ })
+ .catch((error) => {
+ console.error("Error fetching data:", error);
+ });
+ };
+
+ const addGroup = () => {
+ fetch(`${apiEndpoint}/group/add`, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({
+ name: name,
+ username: username,
+ }),
+ })
+ .then((response) => {
+ if (!response.ok) {
+ throw new Error("Failed to create group");
+ }
+ return response.json();
+ })
+ .then(() => {
+ setAlertMessage("Group created successfully");
+ setName("");
+ })
+ .catch((error) => {
+ setError(error.message);
+ });
+ };
+
+ const handleJoinGroup = (groupId) => {
+ fetch(`${apiEndpoint}/group/join`, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({
+ groupId: groupId,
+ username: localStorage.getItem("username"),
+ }),
+ })
+ .then((response) => {
+ if (!response.ok) {
+ throw new Error("Failed to join group");
+ }
+
+ setGroups((prevGroups) =>
+ prevGroups.filter((group) => group._id !== groupId)
+ );
+ })
+ .catch((error) => {
+ console.error("Error joining group:", error);
+ });
+ };
+
+ return (
+ <>
+
+
+
+
+ {t("pages.groups.title")}
+
+
+
+ setName(e.target.value)}
+ />
+
+
+
+ {error && (
+
+ {`Error: ${error}`}
+
+ )}
+ {alertMessage && (
+
+ {alertMessage}
+
+ )}
+
+
+
+
+ {t("pages.groups.joinable")}
+
+ {groups.length === 0 ? (
+ {t("pages.groups.nogroups")}
+ ) : (
+
+
+
+ {t("pages.groupsTable.groupName")} |
+ {t("pages.groupsTable.creationDate")} |
+ {t("pages.groupsTable.creator")} |
+ {t("pages.groups.join")} |
+
+
+
+ {groups.map((group) => (
+
+ {group.name} |
+ {new Date(group.createdAt).toLocaleDateString()} |
+ {group.members.length > 0 ? group.members[0] : ""} |
+
+
+ |
+
+ ))}
+
+
+ )}
+
+
+
+ >
+ );
+};
+
+export default Groups;
diff --git a/webapp/src/pages/Social/Groups.test.js b/webapp/src/pages/Social/Groups.test.js
new file mode 100644
index 00000000..b449cec5
--- /dev/null
+++ b/webapp/src/pages/Social/Groups.test.js
@@ -0,0 +1,132 @@
+import React from 'react';
+import { render, screen, waitFor } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import { MemoryRouter } from 'react-router-dom';
+import { I18nextProvider } from "react-i18next";
+import i18n from "../../i18n.js";
+import Groups from './Groups';
+
+const mockedGroups = [
+ {
+ _id: '1',
+ name: 'Group 1',
+ createdAt: '2024-04-11T12:00:00.000Z',
+ members: ['user1', 'user2']
+ },
+ {
+ _id: '2',
+ name: 'Group 2',
+ createdAt: '2024-04-10T12:00:00.000Z',
+ members: ['user3', 'user4']
+ }
+];
+
+global.fetch = jest.fn();
+
+const renderComponentWithRouter = () => {
+ render(
+
+
+
+
+
+ );
+};
+
+describe('Groups component', () => {
+ beforeEach(() => {
+ localStorage.clear();
+ jest.clearAllMocks();
+ });
+
+ test('fetches and displays joinable groups', async () => {
+ localStorage.setItem('username', 'testUser');
+
+ fetch.mockResolvedValueOnce({
+ json: async () => ({ groups: mockedGroups })
+ });
+
+ renderComponentWithRouter();
+
+ await waitFor(() => {
+ expect(screen.getByText('Group 1')).toBeInTheDocument();
+ expect(screen.getByText('Group 2')).toBeInTheDocument();
+ });
+ });
+
+ test('displays error message on group creation failure', async () => {
+ localStorage.setItem('username', 'testUser');
+
+ fetch.mockResolvedValueOnce({
+ json: async () => ({ groups: mockedGroups })
+ });
+
+ fetch.mockResolvedValueOnce({
+ ok: false
+ });
+
+ renderComponentWithRouter();
+
+ const addButton = screen.getByRole('button', { name: 'Crear' });
+ userEvent.click(addButton);
+
+ await waitFor(() => {
+ expect(screen.getByText('Error: Failed to create group')).toBeInTheDocument();
+ });
+ });
+
+ test('successfully joins a group', async () => {
+ localStorage.setItem('username', 'testUser');
+
+ fetch.mockResolvedValueOnce({
+ ok: true,
+ json: async () => ({ groups: mockedGroups })
+ });
+ fetch.mockResolvedValueOnce({ ok: true});
+
+ renderComponentWithRouter();
+
+ await waitFor(() => {
+ expect(screen.getByText('Group 1')).toBeInTheDocument();
+ });
+
+ const joinButton = screen.getByText('Group 1').closest('tr').querySelector('button');
+ userEvent.click(joinButton);
+
+ await waitFor(() => {
+ expect(screen.queryByText('Group 1')).not.toBeInTheDocument();
+ });
+ });
+
+ test('successfully joins both groups', async () => {
+ localStorage.setItem('username', 'testUser');
+
+ fetch.mockResolvedValueOnce({
+ ok: true,
+ json: async () => ({ groups: mockedGroups })
+ });
+ fetch.mockResolvedValueOnce({ ok: true});
+ fetch.mockResolvedValueOnce({ ok: true});
+
+ renderComponentWithRouter();
+
+ await waitFor(() => {
+ expect(screen.getByText('Group 1')).toBeInTheDocument();
+ });
+
+ let joinButton = screen.getByText('Group 1').closest('tr').querySelector('button');
+ userEvent.click(joinButton);
+
+ await waitFor(() => {
+ expect(screen.queryByText('Group 1')).not.toBeInTheDocument();
+ });
+
+ joinButton = screen.getByText('Group 2').closest('tr').querySelector('button');
+ userEvent.click(joinButton);
+
+ await waitFor(() => {
+ expect(screen.queryByText('Group 2')).not.toBeInTheDocument();
+ });
+ });
+
+});
diff --git a/webapp/src/pages/Social/UserGroups.js b/webapp/src/pages/Social/UserGroups.js
new file mode 100644
index 00000000..d06a9353
--- /dev/null
+++ b/webapp/src/pages/Social/UserGroups.js
@@ -0,0 +1,101 @@
+import React, { useState, useEffect } from "react";
+import {
+ Alert,
+ Text,
+ Button,
+ Table,
+ Thead,
+ Tbody,
+ Tr,
+ Th,
+ Td,
+ Flex
+} from "@chakra-ui/react";
+import Nav from "../../components/Nav/Nav.js";
+import Footer from "../../components/Footer/Footer.js";
+import { useNavigate } from "react-router-dom";
+import { useTranslation } from "react-i18next";
+
+const UserGroups = () => {
+ const { t } = useTranslation();
+ const [groups, setGroups] = useState([]);
+ const [error, setError] = useState("");
+ const username = localStorage.getItem("username");
+ const navigate = useNavigate();
+
+ const apiEndpoint =
+ process.env.REACT_APP_API_ENDPOINT || "http://localhost:8000";
+
+ useEffect(() => {
+ fetchData();
+ // eslint-disable-next-line
+ }, []);
+
+ const fetchData = () => {
+ fetch(`${apiEndpoint}/group/list`)
+ .then((response) => response.json())
+ .then((data) => {
+ if (data) {
+ const userGroups = data.groups.filter((group) =>
+ group.members.includes(username)
+ );
+ setGroups(userGroups);
+ } else {
+ setError("Error fetching data: Invalid response format");
+ }
+ })
+ .catch((error) => {
+ setError("Error fetching data: " + error.message);
+ });
+ };
+
+ const seeGroupDetails = (groupName) => {
+ navigate(`/social/grupo/${encodeURIComponent(groupName)}`);
+ };
+
+ return (
+ <>
+
+
+
+ {t("pages.usergroups.title")}
+
+ {error && (
+
+ {`Error: ${error}`}
+
+ )}
+
+
+
+ {t("pages.groupsTable.groupName")} |
+ {t("pages.groupsTable.creationDate")} |
+ {t("pages.groupsTable.creator")} |
+ {t("pages.usergroups.seegroup")} |
+
+
+
+ {groups.map((group) => (
+
+ {group.name} |
+ {new Date(group.createdAt).toLocaleDateString()} |
+ {group.members.length > 0 ? group.members[0] : ""} |
+
+
+ |
+
+ ))}
+
+
+
+
+ >
+ );
+};
+
+export default UserGroups;
diff --git a/webapp/src/pages/Social/UserGroups.test.js b/webapp/src/pages/Social/UserGroups.test.js
new file mode 100644
index 00000000..fd4d9ef2
--- /dev/null
+++ b/webapp/src/pages/Social/UserGroups.test.js
@@ -0,0 +1,89 @@
+import React from 'react';
+import { render, screen, waitFor } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import { MemoryRouter } from 'react-router-dom';
+import { I18nextProvider } from "react-i18next";
+import i18n from "../../i18n.js";
+import UserGroups from './UserGroups';
+
+global.fetch = jest.fn();
+
+const mockedGroups = [
+ {
+ _id: '1',
+ name: 'Group 1',
+ createdAt: '2024-04-11T12:00:00.000Z',
+ members: ['testuser','user1', 'user2']
+ },
+ {
+ _id: '2',
+ name: 'Group 2',
+ createdAt: '2024-04-10T12:00:00.000Z',
+ members: ['testuser','user3', 'user4']
+ }
+];
+
+describe('UserGroups component', () => {
+
+ beforeEach(() => {
+ localStorage.clear();
+ });
+
+ test('renders component', async () => {
+ localStorage.setItem('username', 'testuser');
+ fetch.mockResolvedValueOnce({ json: async () => ({ groups: [] }) });
+
+ render(
+
+
+
+
+
+ );
+
+ await waitFor(() => {
+ expect(screen.getByText('Nombre del grupo')).toBeInTheDocument();
+ expect(screen.getByText('Fecha de creación')).toBeInTheDocument();
+ expect(screen.getByText('Creador')).toBeInTheDocument();
+ expect(screen.getByText('Ver grupo')).toBeInTheDocument();
+ });
+ });
+
+ test('fetches and displays user groups', async () => {
+ localStorage.setItem('username', 'testuser');
+ fetch.mockResolvedValueOnce({ json: async () => ({ groups: mockedGroups }) });
+
+ render(
+
+
+
+
+
+ );
+
+ await waitFor(() => {
+ expect(screen.getByText('Group 1')).toBeInTheDocument();
+ expect(screen.getByText('Group 2')).toBeInTheDocument();
+ });
+ });
+
+ test('navigates to group details on click', async () => {
+ localStorage.setItem('username', 'testuser');
+ fetch.mockResolvedValueOnce({ json: async () => ({ groups: mockedGroups }) });
+
+ render(
+
+
+
+
+
+ );
+
+ await waitFor(() => {
+ const groupLink = screen.getByText('Group 1');
+ userEvent.click(groupLink);
+ });
+
+ });
+});
+
diff --git a/webapp/src/pages/Social/UsersPage.js b/webapp/src/pages/Social/UsersPage.js
index 9ce152e4..9c8b573d 100644
--- a/webapp/src/pages/Social/UsersPage.js
+++ b/webapp/src/pages/Social/UsersPage.js
@@ -21,7 +21,7 @@ const UserList = ({ users, handleAddFriend }) => {
return (
-
{t("pages.userspage.list")}
+
{t("pages.userspage.list")}
diff --git a/webapp/src/pages/Stats/Stats.js b/webapp/src/pages/Stats/Stats.js
index c8d4ebcf..c2fe44f3 100644
--- a/webapp/src/pages/Stats/Stats.js
+++ b/webapp/src/pages/Stats/Stats.js
@@ -8,37 +8,36 @@ import {
Tr,
Td,
Box,
- Flex
+ Flex,
} from "@chakra-ui/react";
import Nav from "../../components/Nav/Nav.js";
import Footer from "../../components/Footer/Footer.js";
import { useTranslation } from "react-i18next";
const Stats = () => {
- const gatewayUrl = process.env.REACT_APP_API_ENDPOINT || "http://localhost:8000";
+ const gatewayUrl =
+ process.env.REACT_APP_API_ENDPOINT || "http://localhost:8000";
const { t } = useTranslation();
-
- const [username, setUsername] = useState(localStorage.username || "error");
+ const [username, setUsername] = useState(localStorage.username);
const [stats, setStats] = useState(null);
const [gamemode, setGamemode] = useState("clasico");
const [isLoading, setIsLoading] = useState(false);
- const [error, setError] = useState(null);
const [fetched, setFetched] = useState(false);
const fetchStats = (mode) => {
setIsLoading(true);
- fetch(gatewayUrl + `/stats?user=${username}&gamemode=${mode}`)
+ fetch(gatewayUrl + `/stats?username=${username}&gamemode=${mode}`)
.then((response) => response.json())
.then((data) => {
- setStats(data);
+ if (data.error) {
+ setStats(null);
+ } else {
+ setStats(data);
+ }
setIsLoading(false);
})
.catch((error) => {
- console.error("Error al obtener las estadísticas:", error);
- setError(
- error.message || "Ha ocurrido un error al obtener las estadísticas"
- );
setIsLoading(false);
});
};
@@ -57,7 +56,7 @@ const Stats = () => {
const handleGamemodeChange = async (mode) => {
if (mode === gamemode) return;
-
+
setGamemode(mode);
fetchStats(mode);
};
@@ -78,32 +77,12 @@ const Stats = () => {
};
if (isLoading) {
- return (
-
-
{t('pages.stats.loading')}
-
{t('pages.stats.loadingText')}
-
- );
- }
-
- if (error) {
return (
<>
-
-
-
- Error: {error}
-
- {t('pages.stats.searchText')}
-
+ {t("pages.stats.loading")}
+ {t("pages.stats.loadingText")}
>
@@ -113,10 +92,10 @@ const Stats = () => {
return (
<>
-
+
{
onChange={handleUsernameChange}
data-testid="usernameInput"
/>
-
+
-
+
{stats === null && !isLoading && (
- {t('pages.stats.noStats')}
+ {t("pages.stats.noStats")}
)}
{stats && (
- {t('pages.stats.stats')} {stats.username} - {t('pages.stats.mode')} {getModeName()}
+ {t("pages.stats.stats")} {stats.username} -{" "}
+ {t("pages.stats.mode")} {getModeName()}
- {t('pages.stats.gamesPlayed')}
+ {t("pages.stats.gamesPlayed")}
|
{stats.nGamesPlayed} |
- {t('pages.stats.pointsPerGame')}
+ {t("pages.stats.pointsPerGame")}
|
- {stats.avgPoints.toFixed(2)} |
+ {stats.avgPoints && stats.avgPoints.toFixed(2)} |
-
-
- {t('pages.stats.totalpoints')}
- |
- {stats.totalPoints} |
-
- {gamemode !== "calculadora" && (
+
+
+ {t("pages.stats.totalpoints")}
+ |
+ {stats.totalPoints} |
+
+ {gamemode !== "calculadora" && (
<>
- {t('pages.stats.totalCorrect')}
+ {t("pages.stats.totalCorrect")}
|
{stats.totalCorrectQuestions} |
- {t('pages.stats.totalIncorrect')}
+ {t("pages.stats.totalIncorrect")}
|
{stats.totalIncorrectQuestions} |
- {t('pages.stats.correctRatio')}
+ {t("pages.stats.correctRatio")}
|
{stats.ratioCorrect.toFixed(2)}% |
- >
- )}
-
-
- {t('pages.stats.avgTime')}
- |
- {stats.avgTime.toFixed(2)} |
-
+ >
+ )}
+
+
+ {t("pages.stats.avgTime")}
+ |
+ {stats.avgTime.toFixed(2)} |
+
@@ -216,4 +202,3 @@ const Stats = () => {
};
export default Stats;
-
diff --git a/webapp/src/pages/Stats/Stats.test.js b/webapp/src/pages/Stats/Stats.test.js
index dd5342f7..1436eda9 100644
--- a/webapp/src/pages/Stats/Stats.test.js
+++ b/webapp/src/pages/Stats/Stats.test.js
@@ -7,14 +7,45 @@ import { I18nextProvider } from "react-i18next";
import i18n from "../../i18n.js";
const userData = {
- username: "testUser",
- nGamesPlayed: 10,
- avgPoints: 7.0,
- totalPoints: 70,
- totalCorrectQuestions: 20,
- totalIncorrectQuestions: 5,
- ratioCorrect: 80.0,
- avgTime: 15.0,
+ "_id": "65fc58fba0ffce7c435e733e",
+ "username": "admin",
+ "gamemode": "clasico",
+ "nGamesPlayed": 27,
+ "avgPoints": 0.9629629629629629,
+ "totalPoints": 26,
+ "totalCorrectQuestions": 26,
+ "totalIncorrectQuestions": 214,
+ "ratioCorrect": 10.833333333333334,
+ "avgTime": 0.8445061728394961,
+ "__v": 0
+};
+
+const columnHeaders = [
+ "Partidas jugadas",
+ "Puntos por partida",
+ "Puntos totales",
+ "Preguntas correctas totales",
+ "Preguntas incorrectas totales",
+ "Porcentaje de aciertos",
+ "Tiempo por pregunta (s)",
+];
+
+const checkTableHeader = (headerText) => {
+ const headerElement = screen.getByText(headerText);
+ expect(headerElement).toBeInTheDocument();
+};
+
+const checkCellValue = (key, value) => {
+ if (key !== "username" && key !== "_id" && key !== "gamemode" && key!=="__v") {
+ const formattedValue =
+ key === "avgPoints" || key === "avgTime"
+ ? value.toFixed(2)
+ : key === "ratioCorrect"
+ ? value.toFixed(2) + "%"
+ : value.toString();
+ const valueElements = screen.getAllByText(formattedValue);
+ expect(valueElements.length).toBeGreaterThan(0);
+ }
};
const renderComponentWithRouter = () => {
@@ -47,39 +78,46 @@ describe("Stats component", () => {
test("fetches user statistics and displays them", async () => {
localStorage.setItem("username", "testUser");
-
+
global.fetch = jest.fn().mockResolvedValue({
json: jest.fn().mockResolvedValue(userData),
});
-
+
renderComponentWithRouter();
-
+
+ await waitFor(async () => {
+ const table = await screen.findByRole("table");
+ expect(table).toBeInTheDocument();
+ });
+
+ columnHeaders.forEach(checkTableHeader);
+ Object.entries(userData).forEach(([key, value]) => {
+ checkCellValue(key, value);
+ });
+ });
+
+ test("updates user statistics when game mode changes", async () => {
+ localStorage.setItem("username", "testUser");
+
+ global.fetch = jest.fn().mockResolvedValue({
+ json: jest.fn().mockResolvedValue(userData),
+ });
+
+ renderComponentWithRouter();
+
+ await waitFor(() => {
+ expect(screen.queryByText("Cargando ...")).not.toBeInTheDocument();
+ });
+
+ const modeButton = screen.getByTestId("calculator-button");
+ userEvent.click(modeButton);
+
const table = await screen.findByRole("table");
expect(table).toBeInTheDocument();
-
- const columnHeaders = [
- "Partidas jugadas",
- "Puntos por partida",
- "Puntos totales",
- "Preguntas correctas totales",
- "Preguntas incorrectas totales",
- "Porcentaje de aciertos",
- "Tiempo por pregunta (s)",
- ];
- columnHeaders.forEach((headerText) => {
- const headerElement = screen.getByText(headerText);
- expect(headerElement).toBeInTheDocument();
- });
+
+ columnHeaders.forEach(checkTableHeader);
Object.entries(userData).forEach(([key, value]) => {
- if (key !== "username") {
- if (key === "avgPoints" || key === "avgTime") {
- expect(screen.getByText(value.toFixed(2))).toBeInTheDocument();
- } else if (key === "ratioCorrect") {
- expect(screen.getByText(value.toFixed(2) + "%")).toBeInTheDocument();
- } else {
- expect(screen.getByText(value.toString())).toBeInTheDocument();
- }
- }
+ checkCellValue(key, value);
});
});
@@ -87,98 +125,71 @@ describe("Stats component", () => {
const userId = "testUser";
localStorage.setItem("username", userId);
- global.fetch = jest.fn().mockRejectedValue(new Error("Failed to fetch"));
+ global.fetch = jest.fn().mockRejectedValueOnce({
+ json: jest.fn().mockRejectedValueOnce(userData),
+ });
renderComponentWithRouter();
- await screen.findByText("Error: Failed to fetch");
+ await waitFor(() => {
+ screen.findByText("Error: Failed to fetch");
+ });
});
test("updates user statistics when username changes", async () => {
localStorage.setItem("username", "testUser");
-
+
global.fetch = jest.fn().mockResolvedValue({
json: jest.fn().mockResolvedValue(userData),
});
-
+
renderComponentWithRouter();
-
+
await screen.findByRole("table");
-
+
const newUsername = "newUser";
localStorage.setItem("username", newUsername);
-
+
const searchButton = await screen.findByText("Buscar");
userEvent.click(searchButton);
-
+
renderComponentWithRouter();
-
+
expect(fetch).toHaveBeenCalledWith(expect.stringContaining(newUsername));
});
-
- test("updates user statistics when game mode changes", async () => {
+
+ test("fetches and displays user statistics for Human Calculator mode", async () => {
localStorage.setItem("username", "testUser");
-
+
+ userData.ratioCorrect = 0;
+ userData.totalCorrectQuestions = 0;
+ userData.totalIncorrectQuestions = 0;
+ userData.gamemode = "calculadora";
+
global.fetch = jest.fn().mockResolvedValue({
json: jest.fn().mockResolvedValue(userData),
});
-
- renderComponentWithRouter();
- await screen.findByRole("table");
-
- const modeButton = screen.getByRole("button", {
- name: /Batería de sabios/i,
- });
- userEvent.click(modeButton);
-
- await waitFor(() => {
- expect(
- screen.queryByText("WIQ")
- ).toBeInTheDocument();
- });
- });
-
- test('fetches and displays user statistics for Human Calculator mode', async () => {
- localStorage.setItem('username', 'testUser');
-
- userData.ratioCorrect=0;
- userData.totalCorrectQuestions=0;
- userData.totalIncorrectQuestions=0;
-
- global.fetch = jest.fn().mockResolvedValue({
- json: jest.fn().mockResolvedValue(userData)
- });
-
+
renderComponentWithRouter();
-
+
await waitFor(() => {
- expect(screen.queryByText('Cargando ...')).not.toBeInTheDocument();
+ expect(screen.queryByText("Cargando ...")).not.toBeInTheDocument();
});
-
- const modeButton = screen.getByRole('button', { name: /Calculadora humana/i });
+
+ const modeButton = screen.getByTestId("calculator-button");
userEvent.click(modeButton);
-
- const table = await screen.findByRole('table');
+
+ const table = await screen.findByRole("table");
expect(table).toBeInTheDocument();
-
- const columnHeaders = [
- "Partidas jugadas",
- "Puntos por partida",
- "Puntos totales",
- "Preguntas correctas totales",
- "Preguntas incorrectas totales",
- "Porcentaje de aciertos",
- "Tiempo por pregunta (s)",
- ];
-
- columnHeaders.forEach(headerText => {
+
+ columnHeaders.forEach((headerText) => {
const headerElement = screen.getByText(headerText);
expect(headerElement).toBeInTheDocument();
});
-
+
Object.entries(userData).forEach(([key, value]) => {
- if (key !== 'username') {
- if (key === 'avgPoints' || key === 'avgTime') {
+ if (key !== "username" && key!=="_id" && key!=="gamemode") {
+ if (key === "avgPoints" || key === "avgTime") {
const valueElements = screen.getAllByText(value.toFixed(2));
expect(valueElements.length).toBeGreaterThan(0);
} else {
@@ -188,31 +199,32 @@ describe("Stats component", () => {
}
});
});
-
+
test("displays a message when no stats are available", async () => {
localStorage.setItem("username", "testUser");
-
- global.fetch = jest.fn().mockResolvedValueOnce({
- json: jest.fn().mockResolvedValue(null), // Simula que no hay estadísticas disponibles
+
+ global.fetch = jest.fn().mockRejectedValueOnce({
+ json: jest.fn().mockRejectedValueOnce(), // Simula que no hay estadísticas disponibles
});
-
+
renderComponentWithRouter();
+
await waitFor(() => {
- expect(screen.getByText("El usuario no ha jugado ninguna partida.")).toBeInTheDocument();
+ expect(
+ screen.getByText("El usuario no ha jugado ninguna partida.")
+ ).toBeInTheDocument();
});
});
-
+
test("displays loading message while fetching stats", async () => {
localStorage.setItem("username", "testUser");
-
+
global.fetch = jest.fn().mockResolvedValueOnce(new Promise(() => {})); // Simula una promesa pendiente
-
+
renderComponentWithRouter();
-
+
await waitFor(() => {
expect(screen.getByText("Cargando ...")).toBeInTheDocument();
});
});
-
-
});