generated from Arquisoft/wiq_0
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Loading status checks…
v1.0 release
Master update
Showing
54 changed files
with
23,157 additions
and
851 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
global: | ||
scrape_interval: 5s | ||
scrape_configs: | ||
- job_name: "example-nodejs-app" | ||
- job_name: "WIQ" | ||
static_configs: | ||
- targets: ["gatewayservice:8000"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
build/ | ||
src/ | ||
*.js |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,16 @@ | ||
Feature: Registering a new user | ||
Feature: Login a registered user | ||
|
||
Scenario: The user is registered in the site | ||
Given A registered user | ||
When I fill the data in the form and press submit | ||
Then is logged | ||
Given An registered user | ||
When I fill the data in the form to log in | ||
Then is taken to the home page | ||
|
||
Scenario: User logs in with invalid credentials | ||
Given a registered user with username "testUser" and password "testpass" | ||
When I fill the login form with username "testUser" and incorrect password "wrongpass" | ||
And I remain on the login page | ||
|
||
Scenario: User attempts to login without entering credentials | ||
Given a registered user with username "testUser" and password "testpass" | ||
When I attempt to log in without entering any credentials | ||
And I remain on the login page |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
Feature: View and Change User Quiz Rankings | ||
|
||
Scenario: Viewing Global Rankings | ||
Given the user navigates to their profile | ||
When they select the "Global" category | ||
Then they see their performance statistics for global quizzes | ||
|
||
Scenario: Switching Category to Flags | ||
Given the user is on their profile page | ||
When they click on the "Flags" category | ||
Then they view their performance metrics for flag-related quizzes | ||
|
||
Scenario: Switching Category to Food | ||
Given the user is on their profile page | ||
When they click on the "Food" category | ||
Then they view their performance metrics for food-related quizzes | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
const puppeteer = require('puppeteer'); | ||
const { defineFeature, loadFeature }=require('jest-cucumber'); | ||
const setDefaultOptions = require('expect-puppeteer').setDefaultOptions | ||
const feature = loadFeature('./features/userprofile-form.feature'); | ||
|
||
|
||
let page; | ||
let browser; | ||
|
||
defineFeature(feature, test => { | ||
|
||
beforeAll(async () => { | ||
|
||
browser = process.env.GITHUB_ACTIONS | ||
? await puppeteer.launch() | ||
: await puppeteer.launch({ headless: false, slowMo: 20 }); | ||
page = await browser.newPage(); | ||
//Way of setting up the timeout | ||
setDefaultOptions({ timeout: 10000 }) | ||
|
||
await page | ||
.goto("http://localhost:3000/login", { | ||
waitUntil: "networkidle0", | ||
}) | ||
.catch(() => {}); | ||
}); | ||
|
||
test('Viewing Global Rankings', ({given,when,then}) => { | ||
|
||
let username; | ||
let password; | ||
|
||
given('the user navigates to their profile', async () => { | ||
username = "testUser" | ||
password = "testpass"; | ||
await expect(page).toClick("a", { text: "Log In" }); | ||
await expect(page).toFill('input[name="username"]', username); | ||
await expect(page).toFill('input[name="password"]', password); | ||
await expect(page).toClick("button", { text: "Log In" }); | ||
await expect(page).toClick("button", { text: "My stats" }); | ||
}); | ||
|
||
when('they select the "Global" category', async () => { | ||
|
||
await expect(page).toMatchElement("h2", { text: "Username: " + username }); | ||
await expect(page).toClick("button", { text: "Flags" }); | ||
await expect(page).toClick("button", { text: "Global" }); | ||
}); | ||
|
||
then('they see their performance statistics for global quizzes', async () => { | ||
await expect(page).toMatchElement('.ranking h3', { text: "global Ranking" }); | ||
await expect(page).toMatchElement(".ranking p:nth-child(1)", { text: "Total Answered Questions: " + 1 }); | ||
}); | ||
|
||
}); | ||
|
||
test('Switching Category to Flags', ({given,when,then}) => { | ||
|
||
let username; | ||
let password; | ||
|
||
given('the user is on their profile page', async () => { | ||
username = "testUser" | ||
password = "testpass"; | ||
await expect(page).toMatchElement("h2", { text: "Username: " + username }); | ||
}); | ||
|
||
when('they click on the "Flags" category', async () => { | ||
|
||
await expect(page).toClick("button", { text: "Flags" }); | ||
}); | ||
|
||
then('they view their performance metrics for flag-related quizzes', async () => { | ||
await expect(page).toMatchElement('.ranking h3', { text: "flags Ranking" }); | ||
await expect(page).toMatchElement(".ranking p:nth-child(1)", { text: "Total Answered Questions: " + 1 }); | ||
}); | ||
|
||
}); | ||
|
||
test('Switching Category to Food', ({given,when,then}) => { | ||
|
||
let username; | ||
let password; | ||
|
||
given('the user is on their profile page', async () => { | ||
username = "testUser" | ||
password = "testpass"; | ||
await expect(page).toMatchElement("h2", { text: "Username: " + username }); | ||
}); | ||
|
||
when('they click on the "Food" category', async () => { | ||
|
||
await expect(page).toClick("button", { text: "Food" }); | ||
}); | ||
|
||
then('they view their performance metrics for food-related quizzes', async () => { | ||
await expect(page).toMatchElement('.ranking h3', { text: "foods Ranking" }); | ||
await expect(page).toMatchElement(".ranking p:nth-child(1)", { text: "Total Answered Questions: " + 0 }); | ||
}); | ||
|
||
}); | ||
|
||
afterAll(async ()=>{ | ||
browser.close() | ||
}) | ||
|
||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
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'; | ||
import { getByText, getAllByText } from '@testing-library/dom'; | ||
|
||
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(); | ||
//New Imlementation throws Type Error as they are strings | ||
// expect(getAllByText('Play')).toBeInTheDocument(); | ||
// expect(getAllByText('Rankings')).toBeInTheDocument(); | ||
// expect(getAllByText('My Profile')).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(); | ||
//New Imlementation throws Type Error as they are strings | ||
// expect(getAllByText('Play')).toBeInTheDocument(); | ||
// expect(getAllByText('Rankings')).toBeInTheDocument(); | ||
expect(getByText('Log In')).toBeInTheDocument(); | ||
}); | ||
it('calls signOut when Log out button is clicked', () => { | ||
useIsAuthenticated.mockReturnValue(() => true); | ||
useSignOut.mockReturnValue(() => {}); | ||
const { getByText } = render( | ||
<MemoryRouter> | ||
<Navbar /> | ||
</MemoryRouter> | ||
); | ||
|
||
fireEvent.click(getByText('Log out')); | ||
expect(useSignOut).toHaveBeenCalled(); | ||
}); | ||
it | ||
|
||
}); |
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import React from 'react'; | ||
|
||
function Squads() { | ||
return ( | ||
<div class="content-align text-center justify-center bg-[#4c2185]" style={{height: "92.9vh"}}> | ||
<h1 className="text-6xl font-bold mb-8 text-white">Squads</h1> | ||
<h2 className="text-4xl font-body mb-8 text-white">Coming soon...</h2> | ||
</div> | ||
); | ||
} | ||
|
||
export default Squads; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
// src/components/AddUser.js | ||
import React, { useState } from 'react'; | ||
import axios from 'axios'; | ||
import { Container, Typography, TextField, Button, Snackbar } from '@mui/material'; | ||
import { Link } from 'react-router-dom'; | ||
import { useNavigate } from 'react-router-dom'; | ||
const apiEndpoint = process.env.REACT_APP_API_ENDPOINT || 'http://localhost:8000'; | ||
|
||
const AddUser = () => { | ||
const [username, setUsername] = useState(''); | ||
const [email, setEmail] = useState(''); | ||
const [password, setPassword] = useState(''); | ||
const [cpassword, setcPassword] = useState(''); | ||
const [error, setError] = useState(''); | ||
const [openSnackbar, setOpenSnackbar] = useState(false); | ||
const navigate = useNavigate(); | ||
|
||
const addUser = async () => { | ||
try { | ||
if (password !== cpassword) { | ||
setError("Passwords do not match"); | ||
return; | ||
} | ||
await axios.post(`${apiEndpoint}/adduser`, { username, email, password }); | ||
setOpenSnackbar(true); | ||
navigate('/login'); | ||
|
||
} catch (error) { | ||
if (error.response === undefined) { | ||
setError("There was a problem..."); | ||
} | ||
else { | ||
setError(error.response.data.error); | ||
} | ||
} | ||
}; | ||
|
||
const handleCloseSnackbar = () => { | ||
setOpenSnackbar(false); | ||
}; | ||
|
||
return ( | ||
<div className="content-center justify-center bg-gradient-to-br from-purple-900 via-indigo-900 to-blue-900" style={{ height: "92.9vh" }}> | ||
<Container component="main" maxWidth="xs" sx={{ marginTop: 4 }} className='bg-white rounded-xl content-center justify-center'> | ||
<h1 className='py-5 text-4xl font-bold text-[#111827]'> | ||
Register | ||
</h1> | ||
<TextField | ||
name="username" | ||
margin="normal" | ||
fullWidth | ||
label="Username" | ||
value={username} | ||
onChange={(e) => setUsername(e.target.value)} | ||
/> | ||
<TextField | ||
name="email" | ||
type='email' | ||
margin="normal" | ||
fullWidth | ||
label="Email" | ||
value={email} | ||
onChange={(e) => setEmail(e.target.value)} | ||
/> | ||
<TextField | ||
name="password" | ||
margin="normal" | ||
fullWidth | ||
label="Password" | ||
type="password" | ||
value={password} | ||
onChange={(e) => setPassword(e.target.value)} | ||
/> | ||
<TextField | ||
name="cpassword" | ||
|
||
margin="normal" | ||
fullWidth | ||
label="Confirm Password" | ||
type="password" | ||
value={cpassword} | ||
onChange={(e) => setcPassword(e.target.value)} | ||
/> | ||
<div className='py-5 content-center justify-center'> | ||
<Button variant="contained" color="primary" className="flex items-center justify-center px-8 py-4 border border-transparent text-base font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 md:py-4 md:text-lg md:px-10 transition-transform transform-gpu hover:-translate-y-1 hover:shadow-lg" onClick={addUser}> | ||
Register | ||
</Button> | ||
</div> | ||
<Typography component="div" align="center" sx={{ marginTop: 2, marginBottom: 3 }}> | ||
<Link to="/login">Already have an account? Log in here.</Link> | ||
</Typography> | ||
|
||
<Snackbar open={openSnackbar} autoHideDuration={6000} onClose={handleCloseSnackbar} message="User added successfully" /> | ||
{ | ||
error && ( | ||
<Snackbar open={!!error} autoHideDuration={6000} onClose={() => setError('')} message={`Error: ${error}`} /> | ||
) | ||
} | ||
</Container> | ||
</div> | ||
|
||
|
||
); | ||
}; | ||
|
||
export default AddUser; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
import React, { useEffect, useState } from "react"; | ||
import { useNavigate } from "react-router-dom"; | ||
import useIsAuthenticated from "react-auth-kit/hooks/useIsAuthenticated"; | ||
import useAuthUser from "react-auth-kit/hooks/useAuthUser"; | ||
import Question from "./Question"; | ||
|
||
const Game = () => { | ||
const [flagGameStarted, setFlagGameStarted] = useState(false); | ||
const [cityGameStarted, setCityGameStarted] = useState(false); | ||
const [monumentGameStarted, setMonumentGameStarted] = useState(false); | ||
const [touristAttractionGameStarted, setTouristAttractionGameStarted] = useState(false); | ||
const [foodGameStarted, setFoodGameStarted] = useState(false); | ||
const isAuthenticated = useIsAuthenticated(); | ||
const navigate = useNavigate(); | ||
const auth = useAuthUser(); | ||
const startFlagsGame = () => { | ||
setFlagGameStarted(!flagGameStarted); | ||
}; | ||
const startCitiesGame = () => { | ||
setCityGameStarted(!cityGameStarted); | ||
}; | ||
const startMonumentsGame = () => { | ||
setMonumentGameStarted(!monumentGameStarted); | ||
}; | ||
const startTouristAttractionsGame = () => { | ||
setTouristAttractionGameStarted(!touristAttractionGameStarted); | ||
}; | ||
const startFoodsGame = () => { | ||
setFoodGameStarted(!foodGameStarted); | ||
}; | ||
useEffect(() => { | ||
if (!isAuthenticated()) { | ||
navigate("/login"); | ||
} | ||
}, [isAuthenticated, navigate]); | ||
|
||
return ( | ||
<div class="area"> | ||
<ul class="circles"> | ||
<li></li> | ||
<li></li> | ||
<li></li> | ||
<li></li> | ||
<li></li> | ||
<li></li> | ||
<li></li> | ||
<li></li> | ||
<li></li> | ||
<li></li> | ||
</ul> | ||
<div className="bg-gradient-to-br from-purple-900 via-indigo-900 to-blue-900 min-h-screen"> | ||
{isAuthenticated() ? (flagGameStarted || cityGameStarted || monumentGameStarted | ||
|| touristAttractionGameStarted || foodGameStarted) ? ( | ||
<div className="flex justify-center content-center pt-10 h-auto"> | ||
{flagGameStarted && <Question type="imgs" category="flags" />} | ||
{cityGameStarted && <Question type="imgs" category="cities" />} | ||
{monumentGameStarted && <Question type="imgs" category="monuments" />} | ||
{touristAttractionGameStarted && <Question type="imgs" category="tourist_attractions" />} | ||
{foodGameStarted && <Question type="imgs" category="foods" />} | ||
</div> | ||
) : ( | ||
<div className="flex flex-col items-center justify-center"> | ||
<h1 className="text-6xl font-bold text-center text-white pt-5">{auth.username}, Let's Play! Guess the...</h1> | ||
<div className="grid grid-cols-1 p-7 gap-5"> | ||
<button onClick={startFlagsGame} className="w-auto flex items-center justify-center px-8 py-3 border border-transparent text-base font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 md:py-4 md:text-lg md:px-10 | ||
transition-transform transform-gpu hover:scale-105"> | ||
Flag | ||
</button> | ||
<button onClick={startCitiesGame} className="flex items-center justify-center px-8 py-3 border border-transparent text-base font-medium rounded-md text-indigo-700 bg-indigo-100 hover:bg-indigo-200 md:py-4 md:text-lg md:px-10 | ||
transition-transform transform-gpu hover:scale-105"> | ||
City | ||
</button> | ||
<button onClick={startMonumentsGame} className="w-auto flex items-center justify-center px-8 py-3 border border-transparent text-base font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 md:py-4 md:text-lg md:px-10 | ||
transition-transform transform-gpu hover:scale-105"> | ||
Monument | ||
</button> | ||
<button onClick={startTouristAttractionsGame} className="flex items-center justify-center px-8 py-3 border border-transparent text-base font-medium rounded-md text-indigo-700 bg-indigo-100 hover:bg-indigo-200 md:py-4 md:text-lg md:px-10 | ||
transition-transform transform-gpu hover:scale-105"> | ||
Tourist attraction | ||
</button> | ||
<button onClick={startFoodsGame} className="w-auto flex items-center justify-center px-8 py-3 border border-transparent text-base font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 md:py-4 md:text-lg md:px-10 | ||
transition-transform transform-gpu hover:scale-105"> | ||
Food | ||
</button> | ||
</div> | ||
</div> | ||
) : ""} | ||
</div> | ||
</div> | ||
) | ||
}; | ||
|
||
export default Game; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
import React from 'react'; | ||
import { fireEvent, act, render, screen, waitFor } from '@testing-library/react'; | ||
import Game from './Game'; | ||
import { jest } from '@jest/globals'; | ||
import useIsAuthenticated from 'react-auth-kit/hooks/useIsAuthenticated'; | ||
import useAuthUser from 'react-auth-kit/hooks/useAuthUser'; | ||
import axios from 'axios'; | ||
import MockAdapter from 'axios-mock-adapter'; | ||
const mockAxios = new MockAdapter(axios); | ||
|
||
const mock = jest.fn(); | ||
|
||
jest.mock('react-auth-kit/hooks/useIsAuthenticated'); | ||
jest.mock('react-auth-kit/hooks/useAuthUser'); | ||
jest.mock('react-router-dom', () => ({ | ||
useNavigate: () => mock, | ||
})); | ||
|
||
mockAxios.onGet(/\/imgs\/([^\/]+)\/question/).reply(config => { | ||
const category = config.url.match(/\/imgs\/([^\/]+)\/question/)[1]; | ||
return [200, { | ||
question: `${category} question`, | ||
images: ["img1", "img2", "img3", "img4"] | ||
}]; | ||
}); | ||
|
||
describe('Game page', () => { | ||
it('should render play message for authenticated user', async () => { | ||
useIsAuthenticated.mockReturnValue(() => true); | ||
useAuthUser.mockReturnValue({ username: 'testUser' }); | ||
|
||
render(<Game />); | ||
|
||
const playmsg = screen.getByText(/Let's Play! Guess the.../i); | ||
expect(playmsg).toBeInTheDocument(); | ||
}); | ||
|
||
it('shouldnt render play message for unauthenticated user', async () => { | ||
useIsAuthenticated.mockReturnValue(() => false); | ||
try { | ||
const playmsg = screen.getByText(/Let's Play! Guess the.../i); | ||
throw new Error('Unauthenticated user was able to see Game page'); | ||
} catch (err) {} | ||
}); | ||
|
||
it('should render the corresponding question depending on the option clicked', async () => { | ||
useIsAuthenticated.mockReturnValue(() => true); | ||
useAuthUser.mockReturnValue({ username: 'testUser' }); | ||
|
||
let gameOptions = ["Flag","City","Monument","Tourist attraction","Food"] | ||
let gameCategories = ["flags","cities","monuments","tourist_attractions","foods"] | ||
|
||
for(let i=0;i<gameOptions.length;i++){ | ||
render(<Game />); | ||
//Click game option | ||
await act(async ()=>{ | ||
fireEvent.click(screen.getByText(gameOptions[i])) | ||
}) | ||
|
||
//Should have rendered question category | ||
await waitFor(()=>{ | ||
expect(screen.getByText(gameCategories[i]+" question")).toBeInTheDocument() | ||
}) | ||
} | ||
}); | ||
|
||
it('should navigate out of the game if the user is not authenticated', async () => { | ||
useIsAuthenticated.mockReturnValue(() => false); | ||
useAuthUser.mockReturnValue({ username: 'testUser' }); | ||
|
||
render(<Game />); | ||
await waitFor(()=>{ | ||
expect(screen.queryByText("Let's Play!")).not.toBeInTheDocument(); | ||
}) | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
import React, { useEffect } from "react"; | ||
import CheckIcon from '@mui/icons-material/Check'; | ||
import ClearIcon from '@mui/icons-material/Clear'; | ||
import { useNavigate } from "react-router-dom"; | ||
const ImgGameReport = (props) => { | ||
const answers = props.answers; | ||
const navigate = useNavigate(); | ||
const loadRankings = () => { | ||
navigate("/rankings"); | ||
} | ||
return ( | ||
<div > | ||
<div class=" bg-gradient-to-br from-purple-900 via-indigo-900 to-blue-900"> | ||
<div class=" justify-center mx-auto max-w-3xl px-6 py-12"> | ||
<div class="justify-center text-center"> | ||
<h1 class="text-4xl font-bold text-white mb-4">Game Over!</h1> | ||
<p class="text-xl text-white mb-8"> | ||
You answered {props.score} {props.score === 1 ? "question" : "questions"} correctly | ||
</p> | ||
|
||
<div class="sm:flex lg:justify-center"> | ||
<div class="rounded-md shadow"> | ||
<a href="" | ||
onClick={props.restartGame} | ||
class="w-full flex items-center justify-center px-8 py-3 border border-transparent text-base font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 md:py-4 md:text-lg md:px-10 transition-transform transform-gpu hover:-translate-y-1 hover:shadow-lg"> | ||
Restart Game | ||
</a> | ||
</div> | ||
|
||
|
||
|
||
<div class="mt-3 sm:mt-0 sm:ml-3"> | ||
<a href="" | ||
onClick={loadRankings} | ||
class="w-full flex items-center justify-center px-8 py-3 border border-transparent text-base font-medium rounded-md text-indigo-700 bg-indigo-100 hover:bg-indigo-200 md:py-4 md:text-lg md:px-10 transition-transform transform-gpu hover:-translate-y-1 hover:shadow-lg"> | ||
See Rankings | ||
</a> | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
<div class="mt-16 grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-5 gap-4 p-4"> | ||
|
||
|
||
{answers.map((answer) => { | ||
return ( | ||
<div class="flex flex-col rounded-2xl bg-[#ffffff] shadow-xl" key={answer.associate}> | ||
<figure class="flex justify-center items-center rounded-2xl"> | ||
<img src={answer.associate} alt="Card Preview" class="rounded-t-2xl"></img> | ||
</figure> | ||
<div class="flex flex-col p-8"> | ||
<div class="text-2xl font-bold text-center text-[#374151] pb-6">{answer.question}</div> | ||
<div class=" text-base text-center text-[#374151]"> | ||
You answered the question {answer.correct === "true" ? "correctly" : "wrongly"} | ||
{answer.correct === "true" ? <CheckIcon fontSize="large" style={{ color: 'green' }} /> : <ClearIcon fontSize="large" style={{ color: 'red' }} />} | ||
</div> | ||
</div> | ||
</div> | ||
); | ||
})} | ||
</div> | ||
</div> | ||
); | ||
} | ||
|
||
export default ImgGameReport; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,193 @@ | ||
import React, { useState, useEffect } from "react"; | ||
import axios from "axios"; | ||
import useAuthUser from "react-auth-kit/hooks/useAuthUser"; | ||
import ImgGameReport from "./ImgGameReport"; | ||
|
||
const Question = (props) => { | ||
const apiEndpoint = | ||
process.env.REACT_APP_API_ENDPOINT || "http://localhost:8000"; | ||
const [currentQuestion, setCurrentQuestion] = useState(0); | ||
const [questions, setQuestions] = useState([]); | ||
const [loading, setLoading] = useState(true); | ||
const [renderedImages, setRenderedImages] = useState(0); | ||
const [counter, setCounter] = useState(0); | ||
const [score, setScore] = useState(0); | ||
const auth = useAuthUser(); | ||
const questionsPerGame = 10; | ||
const imagesPerQuestion = 4; | ||
|
||
//Game Report | ||
const [finished, setFinished] = useState(false); | ||
const [answers, setAnswers] = useState([]); | ||
|
||
useEffect(() => { | ||
fetchQuestions(); | ||
}, []); | ||
|
||
useEffect(() => { | ||
const interval = setInterval(() => { | ||
if (renderedImages === imagesPerQuestion) { | ||
setCounter((prevCounter) => prevCounter + 0.4); | ||
} | ||
}, 40); | ||
|
||
return () => clearInterval(interval); | ||
}, [renderedImages]); | ||
|
||
useEffect(() => { | ||
async function answ() { | ||
await answerQuestion("TimeOut1234;", questions[currentQuestion].question); | ||
} | ||
|
||
if (counter >= 100 && !loading) { | ||
answ(); | ||
} | ||
}, [counter]); | ||
|
||
const fetchQuestions = async () => { | ||
try { | ||
setRenderedImages(0); | ||
let promises = []; | ||
let questions = []; | ||
for (let i = 0; i < questionsPerGame; i++) { | ||
let question = axios.get( | ||
`${apiEndpoint}/${props.type}/${props.category}/question` | ||
); | ||
promises.push(question); | ||
} | ||
let responses = await Promise.all(promises); | ||
for (let i = 0; i < questionsPerGame; i++) { | ||
let question = responses.pop().data; | ||
questions.push(question); | ||
} | ||
setQuestions(questions); | ||
setLoading(false); | ||
} catch (error) { | ||
console.error("Error fetching question:", error); | ||
} | ||
}; | ||
|
||
const answerQuestion = async (answer, question) => { | ||
if (counter == 0) { | ||
return; | ||
} | ||
try { | ||
setLoading(true); | ||
setRenderedImages(0); | ||
|
||
const result = await axios.post( | ||
`${apiEndpoint}/${props.type}/answer`, | ||
{ | ||
answer: answer, | ||
question: question, | ||
username: auth.username, | ||
category: props.category, | ||
}, | ||
{ headers: { "Content-Type": "application/json" } } | ||
); | ||
|
||
if (result.data.correct === "true") { | ||
setScore(score + 1); | ||
setAnswers( | ||
answers.concat({ | ||
question: question, | ||
correct: result.data.correct, | ||
associate: answer, | ||
}) | ||
); | ||
} else { | ||
setAnswers( | ||
answers.concat({ | ||
question: question, | ||
correct: result.data.correct, | ||
associate: result.data.correctImg, | ||
}) | ||
); | ||
} | ||
|
||
if (currentQuestion >= questionsPerGame - 1) { | ||
//Infinte Game | ||
// fetchQuestions() | ||
// setCurrentQuestion(0) | ||
// setCounter(0); | ||
setFinished(true); | ||
setLoading(false); | ||
return; | ||
} | ||
setCurrentQuestion((question) => question + 1); | ||
setCounter(0); | ||
setLoading(false); | ||
} catch (error) { | ||
//console.log(error); | ||
} | ||
}; | ||
|
||
const restartGame = async () => { | ||
setLoading(true); | ||
setScore(0); | ||
setAnswers([]); | ||
setCurrentQuestion(0); | ||
setCounter(0); | ||
setFinished(false); | ||
await fetchQuestions(); | ||
setLoading(false); | ||
}; | ||
|
||
return finished ? ( | ||
<div className="bg-white"> | ||
<ImgGameReport | ||
score={score} | ||
answers={answers} | ||
restartGame={restartGame} | ||
/> | ||
</div> | ||
) : ( | ||
<div className="bg-slate-100 shadow-lg rounded-md p-4 mx-auto max-w-2xl "> | ||
{loading ? ( | ||
<> | ||
<h1 className="font-bold text-2xl text-gray-800 pl-8"> | ||
<div class="flex justify-center items-center h-fit"> | ||
<div class="rounded-full h-20 w-20 bg-violet-800 animate-ping"></div> | ||
</div> | ||
</h1> | ||
</> | ||
) : ( | ||
<div> | ||
<div className="pl-8 mt-8 p-2">Score: {score}</div> | ||
<h1 className="font-bold text-3xl text-gray-800 pl-8"> | ||
{questions[currentQuestion].question} | ||
</h1> | ||
<div class="relative h-5 rounded-full overflow-hidden bg-gray-300 mt-20 mx-10"> | ||
<div | ||
class="absolute top-0 bottom-0 left-0 rounded-full bg-gradient-to-r from-pink-500 to-purple-500" | ||
style={{ width: counter + "%" }} | ||
data-testid="time-bar" | ||
></div> | ||
</div> | ||
<div className="grid grid-cols-2 mt-10 item"> | ||
{questions[currentQuestion].images.map((image) => ( | ||
<button className="transition-transform transform-gpu hover:scale-105 rounded-xl mx-8 my-8 max-h-52 max-w-80"> | ||
<img | ||
src={image} | ||
alt="Loading ..." | ||
className="rounded-lg object-contain shadow-md" | ||
onClick={async () => | ||
await answerQuestion( | ||
image, | ||
questions[currentQuestion].question | ||
) | ||
} | ||
onLoad={() => | ||
setRenderedImages((renderedImages) => renderedImages + 1) | ||
} | ||
></img> | ||
</button> | ||
))} | ||
</div> | ||
</div> | ||
)} | ||
</div> | ||
); | ||
}; | ||
|
||
export default Question; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,211 @@ | ||
import React from 'react'; | ||
import { render, fireEvent, screen, waitFor, act, getByRole, getAllByRole } from '@testing-library/react'; | ||
import Question from './Question'; | ||
import useAuthUser from 'react-auth-kit/hooks/useAuthUser'; | ||
import MockAdapter from 'axios-mock-adapter'; | ||
import axios from 'axios'; | ||
const mockAxios = new MockAdapter(axios); | ||
|
||
const mock = jest.fn(); | ||
|
||
jest.mock('react-auth-kit/hooks/useAuthUser'); | ||
jest.mock('react-router-dom', () => ({ | ||
useNavigate: () => mock, | ||
})); | ||
|
||
async function loadImages(){ | ||
await act(async () => { | ||
fireEvent.load(screen.getAllByRole("img")[0]) | ||
fireEvent.load(screen.getAllByRole("img")[1]) | ||
fireEvent.load(screen.getAllByRole("img")[2]) | ||
fireEvent.load(screen.getAllByRole("img")[3]) | ||
}); | ||
} | ||
|
||
async function waitForTimeBarStart(){ | ||
await waitFor(() => { | ||
const time_bar = screen.getByTestId('time-bar'); | ||
expect(time_bar).toBeInTheDocument(); | ||
const widthStyle = time_bar.style.width; | ||
const widthValue = parseFloat(widthStyle); | ||
expect(widthValue).toBeGreaterThan(0); | ||
}); | ||
} | ||
|
||
describe('Question page', () => { | ||
beforeEach(() => { | ||
mockAxios.reset(); | ||
}); | ||
|
||
it('should render a question of flag images if type is image and category is flags', async () => { | ||
useAuthUser.mockReturnValue({ username: 'testUser' }); | ||
|
||
mockAxios.onGet('http://localhost:8000/imgs/flags/question').reply(200, | ||
{ | ||
question: "Which of the following flags belongs to Spain?", | ||
images:["https://commons.wikimedia.org/wiki/File:Flag_of_Spain.svg" | ||
,"https://commons.wikimedia.org/wiki/File:Flag_of_England.svg" | ||
,"https://commons.wikimedia.org/wiki/File:Flag_of_Poland.svg" | ||
,"https://commons.wikimedia.org/wiki/File:Flag_of_Germany.svg"] | ||
}); | ||
|
||
render(<Question type="imgs" category="flags"/>); | ||
|
||
await waitFor(() => { | ||
expect(screen.getByText(/Which of the following flags belongs to/i)).toBeInTheDocument(); | ||
expect(screen.getByText(/Score/i)).toBeInTheDocument(); | ||
expect(screen.getByText(/Score/i).textContent).toBe("Score: 0") | ||
let imgs = [] | ||
imgs = screen.getAllByRole("button") | ||
expect(imgs.length).toBe(4) | ||
}); | ||
}); | ||
|
||
it('should render a question of food images if type is image and category is foods', async () => { | ||
useAuthUser.mockReturnValue({ username: 'testUser' }); | ||
|
||
mockAxios.onGet('http://localhost:8000/imgs/foods/question').reply(200, | ||
{ | ||
question: "Which of the following images corresponds to Tortilla?", | ||
images:["TortillaImage","PaellaImage","CachopoImage","ChocoImage"] | ||
}); | ||
|
||
render(<Question type="imgs" category="foods"/>); | ||
|
||
await waitFor(() => { | ||
expect(screen.getByText(/Which of the following images corresponds to/i)).toBeInTheDocument(); | ||
expect(screen.getByText(/Score/i)).toBeInTheDocument(); | ||
expect(screen.getByText(/Score/i).textContent).toBe("Score: 0") | ||
let imgs = [] | ||
imgs = screen.getAllByRole("button") | ||
expect(imgs.length).toBe(4) | ||
}); | ||
}); | ||
|
||
it('should handle a fetching question error', async () => { | ||
useAuthUser.mockReturnValue({ username: 'testUser' }); | ||
|
||
mockAxios.onGet('http://localhost:8000/imgs/foods/question').networkError(); | ||
|
||
render(<Question type="imgs" category="foods"/>); | ||
}); | ||
|
||
it('should update the score if the answer is correct', async () => { | ||
useAuthUser.mockReturnValue({ username: 'testUser' }); | ||
|
||
mockAxios.onGet('http://localhost:8000/imgs/flags/question').reply(200, | ||
{ | ||
question: "Which of the following flags belongs to Spain?", | ||
images:["https://commons.wikimedia.org/wiki/File:Flag_of_Spain.svg" | ||
,"https://commons.wikimedia.org/wiki/File:Flag_of_England.svg" | ||
,"https://commons.wikimedia.org/wiki/File:Flag_of_Poland.svg" | ||
,"https://commons.wikimedia.org/wiki/File:Flag_of_Germany.svg"] | ||
}); | ||
|
||
mockAxios.onPost('http://localhost:8000/imgs/answer').reply(200, | ||
{ | ||
correct: "true" | ||
}); | ||
|
||
render(<Question type="imgs" category="flags"/>); | ||
|
||
await waitFor(() => { | ||
expect(screen.getByText(/Score/i).textContent).toBe("Score: 0") | ||
expect(screen.getByText(/Which of the following/i)).toBeInTheDocument(); | ||
}); | ||
|
||
await loadImages() | ||
await waitForTimeBarStart() | ||
|
||
await act(async () => { | ||
fireEvent.click(screen.getAllByRole("img")[0]); | ||
}); | ||
|
||
await waitFor(() => { | ||
expect(screen.getByText(/Score/i).textContent).toBe("Score: 1") | ||
}) | ||
}); | ||
|
||
it('should not update the score if the answer is incorrect', async () => { | ||
useAuthUser.mockReturnValue({ username: 'testUser' }); | ||
|
||
mockAxios.onGet('http://localhost:8000/imgs/flags/question').reply(200, | ||
{ | ||
question: "Which of the following flags belongs to Spain?", | ||
images:["https://commons.wikimedia.org/wiki/File:Flag_of_Spain.svg" | ||
,"https://commons.wikimedia.org/wiki/File:Flag_of_England.svg" | ||
,"https://commons.wikimedia.org/wiki/File:Flag_of_Poland.svg" | ||
,"https://commons.wikimedia.org/wiki/File:Flag_of_Germany.svg"] | ||
}); | ||
|
||
mockAxios.onPost('http://localhost:8000/imgs/answer').reply(200, | ||
{ | ||
correct: "false", | ||
correctImg: "https://commons.wikimedia.org/wiki/File:Flag_of_Spain.svg" | ||
}); | ||
|
||
render(<Question type="imgs" category="flags"/>); | ||
|
||
await waitFor(() => { | ||
expect(screen.getByText(/Score/i).textContent).toBe("Score: 0") | ||
expect(screen.getByText(/Which of the following/i)).toBeInTheDocument(); | ||
}); | ||
|
||
await act(async () => { | ||
fireEvent.click(screen.getAllByRole("img")[2]); | ||
}); | ||
|
||
await waitFor(() => { | ||
expect(screen.getByText(/Score/i).textContent).toBe("Score: 0") | ||
}) | ||
}); | ||
|
||
it('should finish the game and render the report, from which game can be restarted', async () => { | ||
useAuthUser.mockReturnValue({ username: 'testUser' }); | ||
|
||
mockAxios.onGet('http://localhost:8000/imgs/flags/question').reply(200, | ||
{ | ||
question: "Which of the following flags belongs to Spain?", | ||
images:["https://commons.wikimedia.org/wiki/File:Flag_of_Spain.svg" | ||
,"https://commons.wikimedia.org/wiki/File:Flag_of_England.svg" | ||
,"https://commons.wikimedia.org/wiki/File:Flag_of_Poland.svg" | ||
,"https://commons.wikimedia.org/wiki/File:Flag_of_Germany.svg"] | ||
}); | ||
|
||
mockAxios.onPost('http://localhost:8000/imgs/answer').reply(200, | ||
{ | ||
correct: "false", | ||
correctImg: "https://commons.wikimedia.org/wiki/File:Flag_of_Spain.svg" | ||
}); | ||
|
||
render(<Question type="imgs" category="flags"/>); | ||
|
||
await waitFor(() => { | ||
expect(screen.getByText(/Which of the following/i)).toBeInTheDocument(); | ||
}); | ||
|
||
const questionsPerGame = 10; | ||
for(let i=0;i<questionsPerGame;i++){ | ||
await loadImages() | ||
await waitForTimeBarStart() | ||
await act(async () => { | ||
fireEvent.click(screen.getAllByRole("img")[2]); | ||
}); | ||
} | ||
|
||
await waitFor(() => { | ||
expect(screen.queryByText("Game Over!")).toBeInTheDocument() | ||
}) | ||
|
||
await act(async ()=>{ | ||
fireEvent.click(screen.getByText("Restart Game")) | ||
}) | ||
|
||
await waitFor(() => { | ||
expect(screen.queryByText(/Which of the following/i)).toBeInTheDocument() | ||
}) | ||
}); | ||
}); | ||
|
||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.