-
-
- {isRoomPrivate ? (
-
- ) : null}
-
-
+
+
+ {isRoomPrivate ? (
+
+ ) : null}
+
);
};
diff --git a/src/components/LoginPage/NameInput/NameInput.module.css b/src/components/LoginPage/NameInput/NameInput.module.css
new file mode 100644
index 0000000..a55980e
--- /dev/null
+++ b/src/components/LoginPage/NameInput/NameInput.module.css
@@ -0,0 +1,20 @@
+.container {
+ display: flex;
+ flex-direction: column;
+ padding: 10px 20px 60px 20px;
+ width: 300px;
+ background: radial-gradient(circle, rgba(0, 138, 255, 1) 5%, rgba(9, 9, 121, 1) 81%);
+ border: 1px solid white;
+ border-radius: 8px;
+ margin: 20px;
+ z-index: 2;
+}
+.container > button {
+ margin-top: 5px;
+ text-align: center;
+ width: 100px;
+ align-self: center;
+}
+.container > input {
+ margin-top: 10px;
+}
diff --git a/src/components/LoginPage/NameInput/NameInput.test.js b/src/components/LoginPage/NameInput/NameInput.test.js
new file mode 100644
index 0000000..44e2702
--- /dev/null
+++ b/src/components/LoginPage/NameInput/NameInput.test.js
@@ -0,0 +1,89 @@
+import React from 'react';
+import { render, screen, fireEvent } from '@testing-library/react';
+import '@testing-library/jest-dom';
+import NameInput from './NameInput';
+import { SocketContext } from '../../../App';
+
+const mockSocket = {
+ on: jest.fn(),
+ emit: jest.fn(),
+};
+
+describe('NameInput component', () => {
+ it('should renders password field when room is private', () => {
+ render(
+
+
+
+ );
+ expect(screen.getByPlaceholderText('Room password')).toBeInTheDocument();
+ });
+
+ it('should not renders password field when room is not private', () => {
+ render(
+
+
+
+ );
+ expect(screen.queryByPlaceholderText('Room password')).not.toBeInTheDocument();
+ });
+
+ it('should handles input change', () => {
+ render(
+
+
+
+ );
+ const nicknameInput = screen.getByPlaceholderText('Nickname');
+ fireEvent.change(nicknameInput, { target: { value: 'TestName' } });
+ expect(nicknameInput.value).toBe('TestName');
+ });
+
+ it('should handles password change', () => {
+ render(
+
+
+
+ );
+ const passwordInput = screen.getByPlaceholderText('Room password');
+ fireEvent.change(passwordInput, { target: { value: 'TestPassword' } });
+ expect(passwordInput.value).toBe('TestPassword');
+ });
+
+ it('should handles button click', () => {
+ render(
+
+
+
+ );
+ const nicknameInput = screen.getByPlaceholderText('Nickname');
+ fireEvent.change(nicknameInput, { target: { value: 'TestName' } });
+ const passwordInput = screen.getByPlaceholderText('Room password');
+ fireEvent.change(passwordInput, { target: { value: 'TestPassword' } });
+ const button = screen.getByText('JOIN');
+ fireEvent.click(button);
+ expect(mockSocket.emit).toHaveBeenCalledWith('player:login', {
+ name: 'TestName',
+ password: 'TestPassword',
+ roomId: 123,
+ });
+ });
+
+ it('should handles Enter key press', () => {
+ render(
+
+
+
+ );
+ const nicknameInput = screen.getByPlaceholderText('Nickname');
+ fireEvent.change(nicknameInput, { target: { value: 'TestName' } });
+ const passwordInput = screen.getByPlaceholderText('Room password');
+ fireEvent.change(passwordInput, { target: { value: 'TestPassword' } });
+ fireEvent.keyDown(nicknameInput, { key: 'Enter' });
+ expect(mockSocket.emit).toHaveBeenCalledWith('player:login', {
+ name: 'TestName',
+ password: 'TestPassword',
+ roomId: 123,
+ });
+ });
+});
diff --git a/src/components/LoginPage/ServerList/ServerList.css b/src/components/LoginPage/ServerList/ServerList.css
deleted file mode 100644
index 318a008..0000000
--- a/src/components/LoginPage/ServerList/ServerList.css
+++ /dev/null
@@ -1,51 +0,0 @@
-th {
- text-align: left;
-}
-img {
- margin-right: 5px;
- width: 20px;
- height: 20px;
-}
-th,
-td {
- padding: 8px;
- text-align: left;
- height: 50px;
-}
-tr {
- max-height: 50px;
-}
-
-table {
- border-collapse: collapse;
- width: 100%;
-}
-.server-container {
- display: flex;
- height: 500px;
- overflow: scroll;
-}
-.room-name {
- max-width: 150px;
- overflow: hidden;
-}
-/* Firefox */
-* {
- scrollbar-width: auto;
- scrollbar-color: #ffffff rgba(0, 0, 0, 0.1);
-}
-
-/* Chrome, Edge, and Safari */
-*::-webkit-scrollbar {
- background: rgba(0, 0, 0, 0);
- width: 10px;
-}
-
-*::-webkit-scrollbar-track {
- background: rgba(0, 0, 0, 0);
-}
-
-*::-webkit-scrollbar-thumb {
- background-color: #ffffff;
- border-radius: 10px;
-}
diff --git a/src/components/LoginPage/ServerList/ServerList.jsx b/src/components/LoginPage/ServerList/ServerList.jsx
deleted file mode 100644
index 16eb46e..0000000
--- a/src/components/LoginPage/ServerList/ServerList.jsx
+++ /dev/null
@@ -1,77 +0,0 @@
-import React, { useContext, useEffect, useState } from 'react';
-import { SocketContext } from '../../../App';
-import lock from '../../../images/login-page/lock.png';
-import refresh from '../../../images/login-page/refresh.png';
-import ReactLoading from 'react-loading';
-
-import './ServerList.css';
-import NameInput from '../NameInput/NameInput';
-
-const ServerList = () => {
- const socket = useContext(SocketContext);
- const [rooms, setRooms] = useState([]);
- const [joining, setJoining] = useState(false);
- const [clickedRoom, setClickedRoom] = useState(null);
- useEffect(() => {
- socket.emit('room:rooms');
- socket.on('room:rooms', data => {
- data = JSON.parse(data);
- setRooms(data);
- });
- }, [socket]);
-
- const getRooms = () => {
- setRooms(null);
- socket.emit('room:rooms');
- };
-
- const handleJoinClick = room => {
- setClickedRoom(room);
- setJoining(true);
- };
-
- return (
-
-
-
Server List
-
-

-
-
-
- {rooms ? (
-
-
-
- |
- Server |
- #/# |
- Status |
- |
-
-
-
- {rooms.map((room, index) => (
-
- {room.private ? : null} |
- {room.name} |
- {`${room.players.length}/4`} |
- {room.isStarted ? 'started' : 'waiting'} |
-
-
- |
-
- ))}
-
-
- ) : (
-
-
-
- )}
-
- {joining ?
: null}
-
- );
-};
-export default ServerList;
diff --git a/src/components/LoginPage/WindowLayout/WindowLayout.jsx b/src/components/LoginPage/WindowLayout/WindowLayout.jsx
new file mode 100644
index 0000000..ee85e85
--- /dev/null
+++ b/src/components/LoginPage/WindowLayout/WindowLayout.jsx
@@ -0,0 +1,15 @@
+import styles from './WindowLayout.module.css';
+
+const WindowLayout = ({ title, titleComponent, content }) => {
+ return (
+
+
+
{title}
+ {titleComponent}
+
+
{content}
+
+ );
+};
+
+export default WindowLayout;
diff --git a/src/components/LoginPage/LoginPage.css b/src/components/LoginPage/WindowLayout/WindowLayout.module.css
similarity index 76%
rename from src/components/LoginPage/LoginPage.css
rename to src/components/LoginPage/WindowLayout/WindowLayout.module.css
index 3376c6f..ca9f74e 100644
--- a/src/components/LoginPage/LoginPage.css
+++ b/src/components/LoginPage/WindowLayout/WindowLayout.module.css
@@ -1,25 +1,14 @@
-.login-page-container {
- display: flex;
- flex-direction: row;
-
- justify-content: center;
- align-items: flex-start;
- height: 50%;
- width: 100%;
-}
-
-.lp-container {
+.container {
margin: 50px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 500px;
- padding: 20px;
color: white;
}
-.title-container {
+.title {
display: flex;
flex-direction: row;
justify-content: center;
@@ -34,17 +23,21 @@
text-align: center;
}
-.title-container > h1 {
+.title > h1 {
width: 100%;
margin: 0;
padding: 0;
}
-.content-container {
+.content {
display: flex;
flex-direction: column;
+ justify-content: center;
+ align-items: center;
width: 100%;
- padding: 10px;
+ padding-left: 5px;
+ padding-right: 5px;
+ padding-top: 10px;
background-color: rgba(0, 0, 0, 0.5);
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
border-left: 1px solid black;
diff --git a/src/components/LoginPage/WindowLayout/WindowLayout.test.js b/src/components/LoginPage/WindowLayout/WindowLayout.test.js
new file mode 100644
index 0000000..2bd1827
--- /dev/null
+++ b/src/components/LoginPage/WindowLayout/WindowLayout.test.js
@@ -0,0 +1,28 @@
+import React from 'react';
+import { render, screen } from '@testing-library/react';
+import '@testing-library/jest-dom';
+import WindowLayout from './WindowLayout';
+
+jest.mock('./WindowLayout', () => ({ title, titleComponent, content }) => (
+
+
{title}
+
{titleComponent}
+
{content}
+
+));
+
+describe('WindowLayout component', () => {
+ it('should render without crashing', () => {
+ render(
+
Test Title Component }
+ content={
-
+
+
);
};
diff --git a/src/components/Navbar/NameContainer/AnimatedOverlay/AnimatedOverlay.module.css b/src/components/Navbar/NameContainer/AnimatedOverlay/AnimatedOverlay.module.css
new file mode 100644
index 0000000..23f87f4
--- /dev/null
+++ b/src/components/Navbar/NameContainer/AnimatedOverlay/AnimatedOverlay.module.css
@@ -0,0 +1,10 @@
+.overlay {
+ width: 100%;
+ height: 100%;
+ position: absolute;
+ top: 0;
+ left: 0;
+ opacity: 0.9;
+ animation: timerAnimation 15s linear infinite;
+ transition-duration: 15s;
+}
diff --git a/src/components/Navbar/NameContainer/AnimatedOverlay/AnimatedOverlay.test.js b/src/components/Navbar/NameContainer/AnimatedOverlay/AnimatedOverlay.test.js
new file mode 100644
index 0000000..77b8d9e
--- /dev/null
+++ b/src/components/Navbar/NameContainer/AnimatedOverlay/AnimatedOverlay.test.js
@@ -0,0 +1,20 @@
+import React from 'react';
+import { render, screen } from '@testing-library/react';
+import '@testing-library/jest-dom';
+import AnimatedOverlay from './AnimatedOverlay';
+
+describe('AnimatedOverlay component', () => {
+ it('renders without crashing', () => {
+ render();
+ });
+
+ it('applies animation delay based on time prop', () => {
+ const timeNow = Date.now();
+ const time = timeNow + 5000;
+ render();
+ const overlay = screen.getByTestId('animated-overlay');
+ const expectedDelay = 15 - Math.ceil((time - timeNow) / 1000);
+
+ expect(overlay).toHaveStyle({ animationDelay: `-${expectedDelay}s` });
+ });
+});
diff --git a/src/components/Navbar/NameContainer/AnimatedOverlay/TimerAnimation.js b/src/components/Navbar/NameContainer/AnimatedOverlay/TimerAnimation.js
index a8742a4..084300a 100644
--- a/src/components/Navbar/NameContainer/AnimatedOverlay/TimerAnimation.js
+++ b/src/components/Navbar/NameContainer/AnimatedOverlay/TimerAnimation.js
@@ -4,20 +4,20 @@ const steps = 86;
let count = 0;
let s = 'polygon(50% 50%, 50% 0%, 50% 0%';
-for (let i = 50; i <= 100; i += 5) {
+for (let i = 50; i < 100; i += 5) {
s += `, ${i}% 0%`;
handle();
}
-for (let i = 0; i <= 100; i += 5) {
+for (let i = 0; i < 100; i += 5) {
s += `, 100% ${i}%`;
handle();
}
-for (let i = 100; i >= 0; i -= 5) {
+for (let i = 100; i > 0; i -= 5) {
s += `, ${i}% 100%`;
handle();
}
-for (let i = 100; i >= 0; i -= 5) {
+for (let i = 100; i > 0; i -= 5) {
s += `, 0% ${i}%`;
handle();
}
@@ -52,12 +52,13 @@ function handle() {
keyframes.push(step);
count++;
}
-
-document.styleSheets[0].insertRule(
- `
+if (document && document.styleSheets && document.styleSheets[0]) {
+ document.styleSheets[0].insertRule(
+ `
@keyframes timerAnimation {
${keyframes.join('\n')}
}
`,
- document.styleSheets[0].cssRules.length
-);
+ document.styleSheets[0].cssRules.length
+ );
+}
diff --git a/src/components/Navbar/NameContainer/NameContainer.jsx b/src/components/Navbar/NameContainer/NameContainer.jsx
index 14981ee..53ba65d 100644
--- a/src/components/Navbar/NameContainer/NameContainer.jsx
+++ b/src/components/Navbar/NameContainer/NameContainer.jsx
@@ -1,13 +1,11 @@
import React from 'react';
import PropTypes from 'prop-types';
import AnimatedOverlay from './AnimatedOverlay/AnimatedOverlay';
+import styles from './NameContainer.module.css';
const NameContainer = ({ player, time }) => {
return (
-
+
{player.name}
{player.nowMoving ?
: null}
@@ -17,6 +15,7 @@ const NameContainer = ({ player, time }) => {
NameContainer.propTypes = {
player: PropTypes.object,
time: PropTypes.number,
+ testId: PropTypes.string,
};
export default NameContainer;
diff --git a/src/components/Navbar/NameContainer/NameContainer.module.css b/src/components/Navbar/NameContainer/NameContainer.module.css
new file mode 100644
index 0000000..b5ef2f2
--- /dev/null
+++ b/src/components/Navbar/NameContainer/NameContainer.module.css
@@ -0,0 +1,13 @@
+.container {
+ position: relative;
+ min-width: 100px;
+ min-height: 50px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ border: 2px solid white;
+ border-radius: 5px;
+ color: white;
+ font-weight: bold;
+ text-align: center;
+}
diff --git a/src/components/Navbar/NameContainer/NameContainer.test.js b/src/components/Navbar/NameContainer/NameContainer.test.js
new file mode 100644
index 0000000..030f9af
--- /dev/null
+++ b/src/components/Navbar/NameContainer/NameContainer.test.js
@@ -0,0 +1,58 @@
+import React from 'react';
+import { render, screen } from '@testing-library/react';
+import '@testing-library/jest-dom';
+import NameContainer from './NameContainer';
+import { NOT_READY_COLOR } from '../../../constants/colors';
+
+jest.mock('./AnimatedOverlay/AnimatedOverlay.jsx', () => () => {
+ return
;
+});
+
+describe('NameContainer component', () => {
+ let player;
+ let time;
+
+ beforeEach(() => {
+ player = {
+ name: 'TestPlayer',
+ ready: false,
+ color: 'blue',
+ nowMoving: false,
+ };
+ time = 0;
+ });
+
+ it('renders without crashing', () => {
+ render(
);
+ });
+
+ it('renders player name', () => {
+ render(
);
+ expect(screen.getByText(player.name)).toBeInTheDocument();
+ });
+
+ it('applies grey color when player is not ready', () => {
+ player.ready = false;
+ render(
);
+ const container = screen.getByText(player.name).closest('div');
+ expect(container).toHaveStyle({ backgroundColor: NOT_READY_COLOR });
+ });
+
+ it('applies player colors as background when player is ready', () => {
+ player.ready = true;
+ render(
);
+ const container = screen.getByText(player.name).closest('div');
+ expect(container).toHaveStyle({ backgroundColor: player.color });
+ });
+
+ it('renders AnimatedOverlay when player is nowMoving', () => {
+ const movingPlayer = { ...player, nowMoving: true };
+ render(
);
+ expect(screen.getByTestId('animated-overlay')).toBeInTheDocument();
+ });
+
+ it('does not render AnimatedOverlay when player is not nowMoving', () => {
+ render(
);
+ expect(screen.queryByTestId('animated-overlay')).toBeNull();
+ });
+});
diff --git a/src/components/Navbar/Navbar.css b/src/components/Navbar/Navbar.css
deleted file mode 100644
index 1358d4b..0000000
--- a/src/components/Navbar/Navbar.css
+++ /dev/null
@@ -1,55 +0,0 @@
-.dice-container {
- margin-left: 20px;
- margin-right: 20px;
- width: 50px;
- height: 50px;
-}
-.roll {
- cursor: pointer;
-}
-.ready-container {
- display: flex;
- width: 300px;
- justify-content: center;
- align-items: center;
- flex-direction: column;
- flex-flow: row-reverse;
- background-color: grey;
- border-radius: 10px;
- border: 2px solid white;
-}
-.ready-container > label {
- margin-left: 10px;
- margin-right: 10px;
- width: 100px;
- color: white;
-}
-.player-container {
- display: flex;
- align-items: center;
- flex-direction: row;
- width: 100%;
-}
-.red {
- margin-bottom: 50px;
- grid-column: 1;
- grid-row: 1;
-}
-.yellow {
- margin-bottom: 50px;
- flex-flow: row-reverse;
- grid-column: 2;
- grid-row: 1;
-}
-.blue {
- margin-top: 50px;
- grid-column: 1;
- grid-row: 4;
-}
-.green {
- margin-top: 50px;
- flex-flow: row-reverse;
- grid-column: 2;
- grid-row: 4;
-}
-/* Styl dla overlay */
diff --git a/src/components/Navbar/Navbar.jsx b/src/components/Navbar/Navbar.jsx
index 1b05d12..608b6e0 100644
--- a/src/components/Navbar/Navbar.jsx
+++ b/src/components/Navbar/Navbar.jsx
@@ -1,29 +1,32 @@
import React from 'react';
-import Dice from '../Gameboard/Dice/Dice';
+import Dice from './Dice/Dice';
import NameContainer from './NameContainer/NameContainer';
import ReadyButton from './ReadyButton/ReadyButton';
-import './Navbar.css';
+import { PLAYER_COLORS } from '../../constants/colors';
import { useContext } from 'react';
import { PlayerDataContext } from '../../App';
-const Navbar = ({ players, started, time, isReady, rolledNumber, nowMoving, rolledNumberCallback, movingPlayer }) => {
+import styles from './Navbar.module.css';
+
+const Navbar = ({ players, started, time, isReady, rolledNumber, nowMoving, movingPlayer }) => {
const context = useContext(PlayerDataContext);
- const colors = ['red', 'blue', 'green', 'yellow'];
+
+ const diceProps = {
+ rolledNumber,
+ nowMoving,
+ movingPlayer,
+ };
+
return (
<>
{players.map((player, index) => (
-
+
-
- {context.color !== player.color || started ? null : }
+ {started ? : null}
+ {context.color === player.color && !started ? : null}
))}
>
);
};
+
export default Navbar;
diff --git a/src/components/Navbar/Navbar.module.css b/src/components/Navbar/Navbar.module.css
new file mode 100644
index 0000000..e574f19
--- /dev/null
+++ b/src/components/Navbar/Navbar.module.css
@@ -0,0 +1,28 @@
+.playerContainer {
+ display: flex;
+ align-items: center;
+ flex-direction: row;
+ width: 100%;
+}
+.red {
+ margin-bottom: 50px;
+ grid-column: 1;
+ grid-row: 1;
+}
+.yellow {
+ margin-bottom: 50px;
+ flex-flow: row-reverse;
+ grid-column: 2;
+ grid-row: 1;
+}
+.blue {
+ margin-top: 50px;
+ grid-column: 1;
+ grid-row: 4;
+}
+.green {
+ margin-top: 50px;
+ flex-flow: row-reverse;
+ grid-column: 2;
+ grid-row: 4;
+}
diff --git a/src/components/Navbar/Navbar.test.js b/src/components/Navbar/Navbar.test.js
new file mode 100644
index 0000000..a9f7457
--- /dev/null
+++ b/src/components/Navbar/Navbar.test.js
@@ -0,0 +1,72 @@
+import React from 'react';
+import { render, screen } from '@testing-library/react';
+import '@testing-library/jest-dom';
+import Navbar from './Navbar';
+import { PlayerDataContext } from '../../App';
+
+const mockPlayers = [
+ { name: 'Player1', color: 'red' },
+ { name: 'Player2', color: 'blue' },
+];
+
+const mockPlayerData = {
+ color: 'red',
+};
+
+jest.mock('./NameContainer/NameContainer.jsx', () => () => {
+ return
;
+});
+
+jest.mock('./ReadyButton/ReadyButton.jsx', () => () => {
+ return
;
+});
+
+jest.mock('./Dice/Dice.jsx', () => () => {
+ return
;
+});
+
+const setup = props => {
+ props.players = mockPlayers;
+ return render(
+
+
+
+ );
+};
+
+describe('Navbar component', () => {
+ it('should render NameContainer for each player', () => {
+ setup({
+ started: true,
+ });
+ expect(screen.getAllByTestId('name-container')).toHaveLength(mockPlayers.length);
+ });
+
+ it('should render Dice when started is true', () => {
+ setup({
+ started: true,
+ });
+ expect(screen.getAllByTestId('dice-container')).toHaveLength(mockPlayers.length);
+ });
+
+ it('should not render ReadyButton when started is true', () => {
+ setup({
+ started: true,
+ });
+ expect(screen.queryByTestId('ready-button')).toBeNull();
+ });
+
+ it('should render ReadyButton when started is false', () => {
+ setup({
+ started: false,
+ });
+ expect(screen.getByTestId('ready-button')).toBeInTheDocument();
+ });
+
+ it('does not render Dice when started is false', () => {
+ setup({
+ started: false,
+ });
+ expect(screen.queryByTestId('dice-container')).toBeNull();
+ });
+});
diff --git a/src/components/Navbar/ReadyButton/ReadyButton.jsx b/src/components/Navbar/ReadyButton/ReadyButton.jsx
index 86d40ed..4d5a2b2 100644
--- a/src/components/Navbar/ReadyButton/ReadyButton.jsx
+++ b/src/components/Navbar/ReadyButton/ReadyButton.jsx
@@ -1,8 +1,7 @@
import React, { useState, useContext } from 'react';
import { SocketContext } from '../../../App';
import Switch from '@mui/material/Switch';
-import '../Navbar.css';
-import '../NameContainer/AnimatedOverlay/TimerAnimation';
+import styles from './ReadyButton.module.css';
const ReadyButton = ({ isReady }) => {
const socket = useContext(SocketContext);
@@ -13,7 +12,7 @@ const ReadyButton = ({ isReady }) => {
setChecked(!checked);
};
return (
-
+
diff --git a/src/components/Navbar/ReadyButton/ReadyButton.module.css b/src/components/Navbar/ReadyButton/ReadyButton.module.css
new file mode 100644
index 0000000..8656421
--- /dev/null
+++ b/src/components/Navbar/ReadyButton/ReadyButton.module.css
@@ -0,0 +1,17 @@
+.container {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ margin: 10px;
+ flex-direction: column;
+ flex-flow: row-reverse;
+ background-color: grey;
+ border-radius: 10px;
+ border: 2px solid white;
+}
+.container > label {
+ margin-left: 10px;
+ margin-right: 10px;
+ width: 100px;
+ color: white;
+}
diff --git a/src/components/Navbar/ReadyButton/ReadyButton.test.js b/src/components/Navbar/ReadyButton/ReadyButton.test.js
new file mode 100644
index 0000000..325587c
--- /dev/null
+++ b/src/components/Navbar/ReadyButton/ReadyButton.test.js
@@ -0,0 +1,55 @@
+import React from 'react';
+import { render, screen, fireEvent } from '@testing-library/react';
+import '@testing-library/jest-dom';
+import ReadyButton from './ReadyButton';
+import { SocketContext } from '../../../App';
+
+const mockSocket = {
+ emit: jest.fn(),
+};
+
+describe('ReadyButton component', () => {
+ it('renders without crashing', () => {
+ render(
+
+
+
+ );
+ });
+
+ it('emits "player:ready" event and toggles switch on change', () => {
+ render(
+
+
+
+ );
+
+ const switchElement = screen.getByRole('checkbox');
+ fireEvent.click(switchElement);
+
+ expect(mockSocket.emit).toHaveBeenCalledWith('player:ready');
+ expect(switchElement).toBeChecked();
+ });
+
+ it('displays correct label when switch is checked', () => {
+ render(
+
+
+
+ );
+
+ const labelElement = screen.getByText('I want to play');
+ expect(labelElement).toBeInTheDocument();
+ });
+
+ it('displays correct label when switch is not checked', () => {
+ render(
+
+
+
+ );
+
+ const labelElement = screen.getByText('Im waiting');
+ expect(labelElement).toBeInTheDocument();
+ });
+});
diff --git a/src/components/Overlay/Overlay.jsx b/src/components/Overlay/Overlay.jsx
new file mode 100644
index 0000000..30164d3
--- /dev/null
+++ b/src/components/Overlay/Overlay.jsx
@@ -0,0 +1,9 @@
+import styles from './Overlay.module.css';
+import useKeyPress from '../../hooks/useKeyPress';
+
+const Overlay = ({ children, handleOverlayClose }) => {
+ useKeyPress('Escape', handleOverlayClose);
+
+ return
{children}
;
+};
+export default Overlay;
diff --git a/src/components/Overlay/Overlay.module.css b/src/components/Overlay/Overlay.module.css
new file mode 100644
index 0000000..115d2ae
--- /dev/null
+++ b/src/components/Overlay/Overlay.module.css
@@ -0,0 +1,13 @@
+.container {
+ position: absolute;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background-color: rgba(0, 0, 0, 0.5);
+ z-index: 1;
+ cursor: pointer;
+}
diff --git a/src/components/Overlay/Overlay.test.js b/src/components/Overlay/Overlay.test.js
new file mode 100644
index 0000000..c7907be
--- /dev/null
+++ b/src/components/Overlay/Overlay.test.js
@@ -0,0 +1,34 @@
+import '@testing-library/jest-dom';
+import React from 'react';
+import { render } from '@testing-library/react';
+import Overlay from './Overlay';
+import userEvent from '@testing-library/user-event';
+
+describe('Overlay component', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it('renders Overlay component', () => {
+ const { container } = render(
{}} />);
+ expect(container).toBeInTheDocument();
+ });
+
+ it('renders children inside Overlay', () => {
+ const { getByTestId } = render(
+ {}}>
+
+
+ );
+ expect(getByTestId('test-child')).toBeInTheDocument();
+ });
+
+ it('calls handleOverlayClose on Escape key press', async () => {
+ const handleOverlayCloseMock = jest.fn();
+ render();
+
+ await userEvent.type(document.body, '{Escape}');
+
+ expect(handleOverlayCloseMock).toHaveBeenCalled();
+ });
+});
diff --git a/src/constants/colors.js b/src/constants/colors.js
new file mode 100644
index 0000000..1e1034e
--- /dev/null
+++ b/src/constants/colors.js
@@ -0,0 +1,2 @@
+export const NOT_READY_COLOR = 'lightgrey';
+export const PLAYER_COLORS = ['red', 'blue', 'green', 'yellow'];
diff --git a/src/constants/diceImages.js b/src/constants/diceImages.js
new file mode 100644
index 0000000..e2acf9c
--- /dev/null
+++ b/src/constants/diceImages.js
@@ -0,0 +1,11 @@
+import one from '../images/dice/1.png';
+import two from '../images/dice/2.png';
+import three from '../images/dice/3.png';
+import four from '../images/dice/4.png';
+import five from '../images/dice/5.png';
+import six from '../images/dice/6.png';
+import roll from '../images/dice/roll.png';
+
+const diceImages = [one, two, three, four, five, six, roll];
+
+export default diceImages;
diff --git a/src/constants/positions.js b/src/constants/positions.js
index c437f9f..c5d848a 100644
--- a/src/constants/positions.js
+++ b/src/constants/positions.js
@@ -1,4 +1,4 @@
-const positions = [
+const positionMapCoords = [
// Red base
{ x: 67, y: 67 }, // 0
{ x: 67, y: 116 },
@@ -114,4 +114,4 @@ const positions = [
{ x: 230, y: 200 }, // 91
];
-export default positions;
+export default positionMapCoords;
diff --git a/src/hooks/useInput.js b/src/hooks/useInput.js
index 1144f96..a933dc3 100644
--- a/src/hooks/useInput.js
+++ b/src/hooks/useInput.js
@@ -1,11 +1,11 @@
import { useState } from 'react';
-export default function useInput({ initialValue }) {
+export default function useInput(initialValue = '') {
const [value, setValue] = useState(initialValue);
const handleChange = e => {
setValue(e.target.value);
};
return {
- value,
+ value: value,
onChange: handleChange,
};
}
diff --git a/src/hooks/useKeyPress.js b/src/hooks/useKeyPress.js
new file mode 100644
index 0000000..70e4a19
--- /dev/null
+++ b/src/hooks/useKeyPress.js
@@ -0,0 +1,15 @@
+import { useEffect } from 'react';
+export default function useKeyPress(targetKey, callback) {
+ const keyPressHandler = ({ key }) => {
+ if (key === targetKey) {
+ callback();
+ }
+ };
+
+ useEffect(() => {
+ window.addEventListener('keydown', keyPressHandler);
+ return () => {
+ window.removeEventListener('keydown', keyPressHandler);
+ };
+ }, [keyPressHandler]);
+}
diff --git a/src/hooks/useSocketData.js b/src/hooks/useSocketData.js
new file mode 100644
index 0000000..527bc84
--- /dev/null
+++ b/src/hooks/useSocketData.js
@@ -0,0 +1,19 @@
+import { useState, useContext } from 'react';
+import { SocketContext } from '../App';
+
+const useSocketData = port => {
+ const socket = useContext(SocketContext);
+ const [data, setData] = useState(null);
+ socket.on(port, res => {
+ let parsedData;
+ try {
+ parsedData = JSON.parse(res);
+ } catch (error) {
+ parsedData = res;
+ }
+ setData(parsedData);
+ });
+ return [data, setData];
+};
+
+export default useSocketData;
diff --git a/src/images/architecture.png b/src/images/architecture.png
new file mode 100644
index 0000000..46bf0a6
Binary files /dev/null and b/src/images/architecture.png differ
diff --git a/src/index.css b/src/index.css
index 1346031..a7f45a9 100644
--- a/src/index.css
+++ b/src/index.css
@@ -20,28 +20,11 @@ canvas {
border-radius: 15px;
border: 2px solid black;
}
-.dice-container > img {
- width: 50px;
- height: 50px;
-}
.navbar-container {
display: flex;
flex-direction: row;
}
-.name-container {
- position: relative;
- min-width: 100px;
- min-height: 50px;
- display: flex;
- justify-content: center;
- align-items: center;
- border: 2px solid white;
- border-radius: 5px;
- color: white;
- font-weight: bold;
- text-align: center;
-}
.timer {
background-color: darkblue;
color: white;
@@ -84,3 +67,70 @@ canvas {
grid-column: 1 / span 2;
grid-row: 2 / span 2;
}
+
+input,
+button {
+ padding: 0;
+ border: none;
+ outline: none;
+ box-sizing: border-box;
+}
+
+input {
+ width: 100%;
+ padding: 12px;
+ font-size: 16px;
+ border-radius: 8px;
+ color: white;
+ border: 1px solid #ccc;
+ background-color: rgba(0, 0, 0, 0.2);
+ transition: border-color 0.3s ease-in-out, background-color 0.3s ease-in-out;
+}
+
+input:disabled {
+ background-color: black;
+ color: #999;
+ border: 1px solid #ddd;
+}
+
+input:focus {
+ color: black;
+ border-color: #4a90e2;
+ background-color: #fff;
+}
+
+button {
+ padding: 12px 20px;
+ font-size: 16px;
+ border-radius: 8px;
+ border: none;
+ color: #fff;
+ background-color: rgba(0, 0, 0, 0.4);
+ cursor: pointer;
+ transition: background-color 0.3s ease-in-out;
+}
+
+button:hover {
+ background-color: rgba(0, 0, 0, 1);
+}
+
+/* Firefox */
+* {
+ scrollbar-width: auto;
+ scrollbar-color: #ffffff rgba(0, 0, 0, 0.1);
+}
+
+/* Chrome, Edge, and Safari */
+*::-webkit-scrollbar {
+ background: rgba(0, 0, 0, 0);
+ width: 10px;
+}
+
+*::-webkit-scrollbar-track {
+ background: rgba(0, 0, 0, 0);
+}
+
+*::-webkit-scrollbar-thumb {
+ background-color: #ffffff;
+ border-radius: 10px;
+}