Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

User profile, user profile test and navbar test #126

Merged
merged 10 commits into from
Apr 24, 2024
21 changes: 21 additions & 0 deletions gatewayservice/gateway-service.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,27 @@ app.get('/rankings/:filter', async (req, res) => {
}
});


app.get('/ranking/user', async (req, res) => {
const username = req.query.username;
const category = req.query.category;

try {
// Forward the request to the user service
const result = await axios.get(`${userServiceUrl}/ranking/user`, {
params: {
username: username,
category: category
}
});

res.json(result.data);

} catch (error) {
res.status(error.response.status).json({ error: error.response.data.error });
}
});

// Read the OpenAPI YAML file synchronously
openapiPath='./openapi.yaml'
if (fs.existsSync(openapiPath)) {
Expand Down
32 changes: 32 additions & 0 deletions users/userservice/user-service.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,38 @@ app.get('/rankings/:filter', async (req, res) => {
})


// Get the ranking info for a specified category and user
app.get('/ranking/user', async (req, res) => {
const username = req.query.username;
const category = req.query.category;

try {
// Fetch the user with the specified username
const user = await User.findOne({ username });

// If user not found, return error
if (!user) {
return res.status(400).json("Error: User not found");
}

// Extract ranking info for the specified category
const rankingInfo = {
username: user.username,
category,
points: user.ranking[category].points,
questions: user.ranking[category].questions,
correct: user.ranking[category].correct,
wrong: user.ranking[category].wrong
};

res.status(200).json(rankingInfo);
} catch (error) {
res.status(400).json({ error: error.message });
}
});



app.post("/addpoints", async (req, res) => {
const username = req.body.username;
const category = req.body.category;
Expand Down
2 changes: 1 addition & 1 deletion webapp/e2e/steps/register-form.steps.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ defineFeature(feature, test => {
beforeAll(async () => {
browser = process.env.GITHUB_ACTIONS
? await puppeteer.launch()
: await puppeteer.launch({ headless: false, slowMo:5 });
: await puppeteer.launch({ headless: false, slowMo:60 });
page = await browser.newPage();
//Way of setting up the timeout
setDefaultOptions({ timeout: 10000 })
Expand Down
3 changes: 3 additions & 0 deletions webapp/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import RankingsLayout from './components/ranking/RankingLayout';
import Game from './components/Game';
import MainPage from './components/MainPage';
import Squads from './components/Squads';
import UserProfile from './components/UserProfile';


function App() {
// const isAuthenticated = useIsAuthenticated() // True if user has logged in
Expand All @@ -25,6 +27,7 @@ function App() {
<Route path='/rankings' element={<RankingsLayout />} />
<Route path='/play' element={<Game />} />
<Route path='/squads' element={<Squads />} />
<Route path='/userprofile' element={<UserProfile />} />
</Routes>
</BrowserRouter>
)
Expand Down
4 changes: 4 additions & 0 deletions webapp/src/components/Navbar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ function Navbar() {
}

return (

<nav class="bg-white border-gray-200 dark:bg-gray-900">
<div class="max-w-screen-xl flex flex-wrap items-center justify-between mx-auto p-4">
<a class="flex items-center space-x-3 rtl:space-x-reverse">
Expand All @@ -38,6 +39,9 @@ function Navbar() {
<li>
<Link class="block py-2 px-3 text-gray-900 rounded hover:bg-gray-100 md:hover:bg-transparent md:hover:text-blue-700 md:p-0 md:dark:hover:text-blue-500 dark:text-white dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent dark:border-gray-700" to="/rankings">Rankings</Link>
</li>
{isAuthenticated() ? (
<li><Link class="block py-2 px-3 text-gray-900 rounded hover:bg-gray-100 md:hover:bg-transparent md:hover:text-blue-700 md:p-0 md:dark:hover:text-blue-500 dark:text-white dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent dark:border-gray-700" to="/userprofile">UserProfile</Link></li>
):"" }
</ul>
</div>
</div>
Expand Down
47 changes: 47 additions & 0 deletions webapp/src/components/Navbar.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import React from 'react';
import { render, fireEvent } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import Navbar from './Navbar';
import useIsAuthenticated from 'react-auth-kit/hooks/useIsAuthenticated';
import useSignOut from 'react-auth-kit/hooks/useSignOut';

jest.mock('react-auth-kit/hooks/useIsAuthenticated');
jest.mock('react-auth-kit/hooks/useSignOut');

describe('Navbar', () => {

beforeEach(() => {
// Reset mock function calls before each test
jest.clearAllMocks();
});

it('renders authenticated user links and logout button', () => {
useIsAuthenticated.mockReturnValue(() => true);
const { getByText } = render(
<MemoryRouter>
<Navbar />
</MemoryRouter>
);

expect(getByText('WIQ')).toBeInTheDocument();
expect(getByText('Play')).toBeInTheDocument();
expect(getByText('Rankings')).toBeInTheDocument();
expect(getByText('UserProfile')).toBeInTheDocument();
expect(getByText('Log out')).toBeInTheDocument();
});

it('renders unauthenticated user links and sign-in link', () => {
useIsAuthenticated.mockReturnValue(() => false);
const { getByText } = render(
<MemoryRouter>
<Navbar />
</MemoryRouter>
);

expect(getByText('WIQ')).toBeInTheDocument();
expect(getByText('Play')).toBeInTheDocument();
expect(getByText('Rankings')).toBeInTheDocument();
expect(getByText('Log In')).toBeInTheDocument();
});

});
75 changes: 75 additions & 0 deletions webapp/src/components/UserProfile.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import React, { useEffect, useState, useCallback } from "react";
import axios from "axios";
import useAuthUser from "react-auth-kit/hooks/useAuthUser";
import userAvatar from "./ranking/profile_img.webp";

const UserProfile = () => {
const auth = useAuthUser();
const [category, setCategory] = useState('global');
const [questions, setQuestions] = useState(0);
const [right, setRight] = useState(0);
const [wrong, setWrong] = useState(0);

const apiEndpoint = process.env.REACT_APP_API_ENDPOINT ||'http://localhost:8000';


const handleCategoryChange = useCallback(async (newCategory) => {
try {
const result = await axios.get(`${apiEndpoint}/ranking/user`, {
params: {
username: auth.username,
category: newCategory
}
});

setQuestions(result.data.questions);
setRight(result.data.correct);
setWrong(result.data.wrong);
setCategory(newCategory);
} catch (error) {
console.error('Error fetching question:', error);
}
}, [apiEndpoint, auth.username]);

useEffect(() => {
handleCategoryChange(category);
}, [handleCategoryChange, category]);


return (
<div className="user-profile">
<div className="user-details">
<div className="user-info">
<img src={userAvatar} alt="User Avatar" className="user-avatar" style={{ width: "300px", height: "300px" }} />
<h2 className="md:p-4 py-3 px-0 font-bold text-gray-600 hover:text-gray-900">Username: {auth.username}</h2>

<p>Email: {auth.email}</p>
<p>Joined: {new Date(auth.createdAt).toLocaleDateString()}</p>


{/* Buttons to change category */}
<div className="category-buttons" style={{ display: 'flex', justifyContent: 'center', marginTop: '10px' }}>
<button style={{ marginRight: '8px', color: 'white', backgroundColor: 'purple', border: 'none', borderRadius: '20px', padding: '10px 20px', cursor: 'pointer' }} onClick={() => handleCategoryChange('global')}>Global</button>
<button style={{ marginRight: '8px', color: 'white', backgroundColor: 'purple', border: 'none', borderRadius: '20px', padding: '10px 20px', cursor: 'pointer' }} onClick={() => handleCategoryChange('flags')}>Flags</button>
<button style={{ marginRight: '8px', color: 'white', backgroundColor: 'purple', border: 'none', borderRadius: '20px', padding: '10px 20px', cursor: 'pointer' }} onClick={() => handleCategoryChange('cities')}>Cities</button>
<button style={{ marginRight: '8px', color: 'white', backgroundColor: 'purple', border: 'none', borderRadius: '20px', padding: '10px 20px', cursor: 'pointer' }} onClick={() => handleCategoryChange('monuments')}>Monuments</button>
<button style={{ marginRight: '8px', color: 'white', backgroundColor: 'purple', border: 'none', borderRadius: '20px', padding: '10px 20px', cursor: 'pointer' }} onClick={() => handleCategoryChange('tourist_attractions')}>Tourist Attractions</button>
<button style={{ color: 'white', backgroundColor: 'purple', border: 'none', borderRadius: '20px', padding: '10px 20px', cursor: 'pointer' }} onClick={() => handleCategoryChange('foods')}>Foods</button>
</div>

<div className="ranking" style={{ textAlign: 'center' }}>
<h3 style={{ marginBottom: '10px', color: 'purple' }}>{category} Ranking</h3>
<div style={{ backgroundColor: '#f4f4f4', padding: '10px', borderRadius: '8px', display: 'inline-block' }}>
<p style={{ marginBottom: '5px' }}>Total Answered Questions: {questions}</p>
<p style={{ marginBottom: '5px', color: 'green' }}>Right Answers: {right}</p>
<p style={{ marginBottom: '5px', color: 'red' }}>Wrong Answers: {wrong}</p>
</div>
</div>

</div>
</div>
</div >
);
};

export default UserProfile;
59 changes: 59 additions & 0 deletions webapp/src/components/UserProfile.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import React from 'react';
import { render, waitFor, fireEvent } from '@testing-library/react';
import UserProfile from './UserProfile';
import axios from 'axios';
import useAuthUser from 'react-auth-kit/hooks/useAuthUser';

jest.mock('axios');
jest.mock('react-auth-kit/hooks/useAuthUser', () => jest.fn());

describe('UserProfile', () => {
beforeEach(() => {
useAuthUser.mockReturnValue({
username: 'testUser',
email: '[email protected]',
createdAt: '2024-01-01T00:00:00Z',
});
});

it('renders user details', async () => {
axios.get.mockResolvedValueOnce({
data: {
questions: 10,
correct: 7,
wrong: 3,
},
});

const { getByText } = render(<UserProfile />);

await waitFor(() => {
expect(getByText('Username: testUser')).toBeInTheDocument();
expect(getByText('Email: [email protected]')).toBeInTheDocument();
expect(getByText('Joined: 1/1/2024')).toBeInTheDocument();
expect(getByText('Total Answered Questions: 10')).toBeInTheDocument();
expect(getByText('Right Answers: 7')).toBeInTheDocument();
expect(getByText('Wrong Answers: 3')).toBeInTheDocument();
});
});

it('changes category when category button is clicked', async () => {
axios.get.mockResolvedValueOnce({
data: {
questions: 5,
correct: 3,
wrong: 2,
},
});

const { getByText } = render(<UserProfile />);

fireEvent.click(getByText('Flags'));

await waitFor(() => {
expect(getByText('Total Answered Questions: 5')).toBeInTheDocument();
expect(getByText('Right Answers: 3')).toBeInTheDocument();
expect(getByText('Wrong Answers: 2')).toBeInTheDocument();
});
});
});
Binary file added webapp/src/components/ranking/profile_img.webp
Binary file not shown.