added tests and css modules to LoginPage components
This commit is contained in:
parent
caf6bc7d91
commit
a33ebeccdd
@ -16,13 +16,13 @@ module.exports = socket => {
|
||||
};
|
||||
|
||||
const handleGetAllRooms = async () => {
|
||||
let rooms = await getRooms();
|
||||
const rooms = await getRooms();
|
||||
sendToOnePlayerRooms(socket.id, rooms);
|
||||
};
|
||||
|
||||
const handleCreateRoom = async data => {
|
||||
createNewRoom(data);
|
||||
socket.to(socket.id).emit('room:created');
|
||||
sendToOnePlayerRooms(socket.id, await getRooms());
|
||||
};
|
||||
|
||||
socket.on('room:data', handleGetData);
|
||||
|
||||
14
src/components/HOC/withLoading.jsx
Normal file
14
src/components/HOC/withLoading.jsx
Normal file
@ -0,0 +1,14 @@
|
||||
import ReactLoading from 'react-loading';
|
||||
|
||||
const withLoading = Component => {
|
||||
return function WithLoading({ isLoading, ...props }) {
|
||||
if (!isLoading) {
|
||||
return <Component {...props} />;
|
||||
}
|
||||
return (
|
||||
<ReactLoading type='spinningBubbles' color='white' height={50} width={50} />
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
export default withLoading;
|
||||
@ -1,35 +0,0 @@
|
||||
.refresh {
|
||||
display: flex;
|
||||
margin-left: auto;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 40px;
|
||||
height: 100%;
|
||||
border: 1px solid white;
|
||||
}
|
||||
.refresh > img {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.private-container {
|
||||
margin-left: 10px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
input:disabled {
|
||||
background-color: black;
|
||||
color: #999;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
@ -1,56 +1,50 @@
|
||||
import React, { useState, useContext, useEffect } from 'react';
|
||||
import './AddServer.css';
|
||||
import React, { useState, useContext } from 'react';
|
||||
import Switch from '@mui/material/Switch';
|
||||
import { SocketContext } from '../../../App';
|
||||
import WindowLayout from '../WindowLayout/WindowLayout';
|
||||
import useInput from '../../../hooks/useInput';
|
||||
import styles from './AddServer.module.css';
|
||||
|
||||
const AddServer = () => {
|
||||
const socket = useContext(SocketContext);
|
||||
const [isPrivate, setIsPrivate] = useState(false);
|
||||
const [serverName, setServerName] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
socket.on('room:created', () => {
|
||||
socket.emit('room:rooms');
|
||||
});
|
||||
}, [socket]);
|
||||
const [isIncorrect, setIsIncorrect] = useState(false);
|
||||
const serverName = useInput('');
|
||||
const password = useInput('');
|
||||
|
||||
const handleButtonClick = e => {
|
||||
e.preventDefault();
|
||||
socket.emit('room:create', {
|
||||
name: serverName,
|
||||
private: isPrivate,
|
||||
password: password,
|
||||
});
|
||||
if (!serverName.value) setIsIncorrect(true);
|
||||
else
|
||||
socket.emit('room:create', {
|
||||
name: serverName.value,
|
||||
password: password.value,
|
||||
private: isPrivate,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='lp-container'>
|
||||
<div className='title-container'>
|
||||
<h1>Host A Server</h1>
|
||||
</div>
|
||||
<div className='content-container'>
|
||||
<form>
|
||||
<WindowLayout
|
||||
title='Host A Server'
|
||||
content={
|
||||
<form className={styles.formContainer}>
|
||||
<input
|
||||
type='text'
|
||||
value={serverName}
|
||||
onChange={e => setServerName(e.target.value)}
|
||||
placeholder='Server Name'
|
||||
{...serverName}
|
||||
style={{
|
||||
border: isIncorrect ? '1px solid red' : '1px solid white',
|
||||
}}
|
||||
/>
|
||||
<div className='private-container'>
|
||||
<p>Private</p>
|
||||
<div className={styles.privateContainer}>
|
||||
<label>Private</label>
|
||||
<Switch checked={isPrivate} color='primary' onChange={() => setIsPrivate(!isPrivate)} />
|
||||
</div>
|
||||
<input
|
||||
type='text'
|
||||
value={password}
|
||||
onChange={e => setPassword(e.target.value)}
|
||||
placeholder='password'
|
||||
disabled={!isPrivate}
|
||||
/>
|
||||
<input type='text' placeholder='password' disabled={!isPrivate} {...password} />
|
||||
<button onClick={handleButtonClick}>Host</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
15
src/components/LoginPage/AddServer/AddServer.module.css
Normal file
15
src/components/LoginPage/AddServer/AddServer.module.css
Normal file
@ -0,0 +1,15 @@
|
||||
.formContainer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.privateContainer {
|
||||
margin-left: 10px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
82
src/components/LoginPage/AddServer/AddServer.test.js
Normal file
82
src/components/LoginPage/AddServer/AddServer.test.js
Normal file
@ -0,0 +1,82 @@
|
||||
import React from 'react';
|
||||
import { render, screen, fireEvent } from '@testing-library/react';
|
||||
import '@testing-library/jest-dom';
|
||||
import { SocketContext } from '../../../App';
|
||||
import AddServer from './AddServer';
|
||||
|
||||
const mockSocket = {
|
||||
emit: jest.fn(),
|
||||
};
|
||||
|
||||
describe('AddServer component', () => {
|
||||
it('should renders without crashing', () => {
|
||||
render(
|
||||
<SocketContext.Provider value={mockSocket}>
|
||||
<AddServer />
|
||||
</SocketContext.Provider>
|
||||
);
|
||||
expect(screen.getByText('Host A Server')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should handles form submission with valid data when private', () => {
|
||||
render(
|
||||
<SocketContext.Provider value={mockSocket}>
|
||||
<AddServer />
|
||||
</SocketContext.Provider>
|
||||
);
|
||||
|
||||
const serverNameInput = screen.getByPlaceholderText('Server Name');
|
||||
fireEvent.change(serverNameInput, { target: { value: 'Test Server' } });
|
||||
|
||||
const privateSwitch = screen.getByRole('checkbox');
|
||||
fireEvent.click(privateSwitch);
|
||||
|
||||
const passwordInput = screen.getByPlaceholderText('password');
|
||||
fireEvent.change(passwordInput, { target: { value: 'TestPassword' } });
|
||||
|
||||
const hostButton = screen.getByText('Host');
|
||||
fireEvent.click(hostButton);
|
||||
|
||||
expect(mockSocket.emit).toHaveBeenCalledWith('room:create', {
|
||||
name: 'Test Server',
|
||||
password: 'TestPassword',
|
||||
private: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('should handles form submission with valid data when not private', () => {
|
||||
render(
|
||||
<SocketContext.Provider value={mockSocket}>
|
||||
<AddServer />
|
||||
</SocketContext.Provider>
|
||||
);
|
||||
|
||||
const serverNameInput = screen.getByPlaceholderText('Server Name');
|
||||
fireEvent.change(serverNameInput, { target: { value: 'Test Server' } });
|
||||
|
||||
const hostButton = screen.getByText('Host');
|
||||
fireEvent.click(hostButton);
|
||||
|
||||
expect(mockSocket.emit).toHaveBeenCalledWith('room:create', {
|
||||
name: 'Test Server',
|
||||
password: '',
|
||||
private: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('should handles form submission with missing server name', () => {
|
||||
render(
|
||||
<SocketContext.Provider value={mockSocket}>
|
||||
<AddServer />
|
||||
</SocketContext.Provider>
|
||||
);
|
||||
|
||||
const hostButton = screen.getByText('Host');
|
||||
fireEvent.click(hostButton);
|
||||
|
||||
expect(mockSocket.emit).not.toHaveBeenCalled();
|
||||
|
||||
const serverNameInput = screen.getByPlaceholderText('Server Name');
|
||||
expect(serverNameInput).toHaveStyle('border: 1px solid red');
|
||||
});
|
||||
});
|
||||
66
src/components/LoginPage/JoinServer/JoinServer.jsx
Normal file
66
src/components/LoginPage/JoinServer/JoinServer.jsx
Normal file
@ -0,0 +1,66 @@
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import { SocketContext } from '../../../App';
|
||||
import refresh from '../../../images/login-page/refresh.png';
|
||||
import NameInput from '../NameInput/NameInput';
|
||||
import Overlay from '../../Overlay/Overlay';
|
||||
import WindowLayout from '../WindowLayout/WindowLayout';
|
||||
import ServersTable from './ServersTable/ServersTable';
|
||||
import withLoading from '../../HOC/withLoading';
|
||||
import useSocketData from '../../../hooks/useSocketData';
|
||||
import styles from './JoinServer.module.css';
|
||||
|
||||
const JoinServer = () => {
|
||||
const socket = useContext(SocketContext);
|
||||
const [rooms, setRooms] = useSocketData('room:rooms');
|
||||
|
||||
const [joining, setJoining] = useState(false);
|
||||
const [clickedRoom, setClickedRoom] = useState(null);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
socket.emit('room:rooms');
|
||||
socket.on('room:rooms', () => {
|
||||
setIsLoading(false);
|
||||
});
|
||||
}, [socket]);
|
||||
|
||||
const getRooms = () => {
|
||||
setRooms([]);
|
||||
socket.emit('room:rooms');
|
||||
};
|
||||
|
||||
const handleJoinClick = room => {
|
||||
setClickedRoom(room);
|
||||
setJoining(true);
|
||||
};
|
||||
|
||||
const ServersTableWithLoading = withLoading(ServersTable);
|
||||
|
||||
return (
|
||||
<>
|
||||
<WindowLayout
|
||||
title='Join A Server'
|
||||
titleComponent={
|
||||
<div className={styles.refresh}>
|
||||
<img src={refresh} alt='refresh' onClick={getRooms} />
|
||||
</div>
|
||||
}
|
||||
content={
|
||||
<div className={styles.serversTableContainer}>
|
||||
<ServersTableWithLoading
|
||||
isLoading={isLoading}
|
||||
rooms={rooms}
|
||||
handleJoinClick={handleJoinClick}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
{joining ? (
|
||||
<Overlay handleOverlayClose={() => setJoining(false)}>
|
||||
<NameInput roomId={clickedRoom._id} isRoomPrivate={clickedRoom.private} />
|
||||
</Overlay>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default JoinServer;
|
||||
20
src/components/LoginPage/JoinServer/JoinServer.module.css
Normal file
20
src/components/LoginPage/JoinServer/JoinServer.module.css
Normal file
@ -0,0 +1,20 @@
|
||||
.serversTableContainer {
|
||||
display: flex;
|
||||
height: 500px;
|
||||
overflow: scroll;
|
||||
width: 100%;
|
||||
}
|
||||
.refresh {
|
||||
display: flex;
|
||||
margin-left: auto;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 40px;
|
||||
height: 100%;
|
||||
border: 1px solid white;
|
||||
}
|
||||
.refresh > img {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
@ -0,0 +1,33 @@
|
||||
import lock from '../../../../images/login-page/lock.png';
|
||||
import styles from './ServersTable.module.css';
|
||||
|
||||
const ServerListTable = ({ rooms, handleJoinClick }) => {
|
||||
return (
|
||||
<table className={styles.rooms}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th className={styles.firstColumn}></th>
|
||||
<th>Server</th>
|
||||
<th>#/#</th>
|
||||
<th>Status</th>
|
||||
<th className={styles.lastColumn}></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{rooms.map((room, index) => (
|
||||
<tr key={index}>
|
||||
<td>{room.private ? <img src={lock} alt='private' /> : null}</td>
|
||||
<td className={styles.roomName}>{room.name}</td>
|
||||
<td>{`${room.players.length}/4`}</td>
|
||||
<td>{room.isStarted ? 'started' : 'waiting'}</td>
|
||||
<td className={styles.lastColumn}>
|
||||
<button onClick={() => handleJoinClick(room)}>Join</button>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
};
|
||||
|
||||
export default ServerListTable;
|
||||
@ -0,0 +1,40 @@
|
||||
.roomName {
|
||||
max-width: 150px;
|
||||
overflow: hidden;
|
||||
text-align: left !important;
|
||||
}
|
||||
.rooms > thead > tr :nth-child(2) {
|
||||
text-align: left;
|
||||
}
|
||||
.rooms > tbody > tr > td > img {
|
||||
margin-right: 5px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.rooms > th {
|
||||
padding: 8px;
|
||||
text-align: center;
|
||||
height: 50px;
|
||||
}
|
||||
.rooms > tbody > tr > td {
|
||||
padding: 4px;
|
||||
text-align: center;
|
||||
height: 50px;
|
||||
}
|
||||
.rooms > tbody > tr > td {
|
||||
max-height: 50px;
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
.rooms {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.lastColumn {
|
||||
width: 70px;
|
||||
}
|
||||
.firstColumn {
|
||||
width: 40px;
|
||||
}
|
||||
@ -0,0 +1,34 @@
|
||||
import React from 'react';
|
||||
import { render, screen, fireEvent } from '@testing-library/react';
|
||||
import '@testing-library/jest-dom';
|
||||
import ServersTable from './ServersTable';
|
||||
|
||||
const mockRooms = [
|
||||
{ _id: '1', name: 'Room 1', private: false, players: [], isStarted: false },
|
||||
{ _id: '2', name: 'Room 2', private: true, players: [], isStarted: true },
|
||||
];
|
||||
|
||||
describe('ServersTable component', () => {
|
||||
it('should renders without crashing', () => {
|
||||
render(<ServersTable rooms={mockRooms} handleJoinClick={() => {}} />);
|
||||
expect(screen.getByText('Server')).toBeInTheDocument();
|
||||
expect(screen.getByText('#/#')).toBeInTheDocument();
|
||||
expect(screen.getByText('Status')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should renders the list of rooms', () => {
|
||||
render(<ServersTable rooms={mockRooms} handleJoinClick={() => {}} />);
|
||||
expect(screen.getByText('Room 1')).toBeInTheDocument();
|
||||
expect(screen.getByText('Room 2')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should handles join click for each room', () => {
|
||||
const handleJoinClick = jest.fn();
|
||||
render(<ServersTable rooms={mockRooms} handleJoinClick={handleJoinClick} />);
|
||||
|
||||
const joinButtons = screen.getAllByText('Join');
|
||||
fireEvent.click(joinButtons[0]);
|
||||
|
||||
expect(handleJoinClick).toHaveBeenCalledWith(mockRooms[0]);
|
||||
});
|
||||
});
|
||||
@ -1,14 +1,13 @@
|
||||
import './LoginPage.css';
|
||||
import AddServer from './AddServer/AddServer';
|
||||
import ServerList from './ServerList/ServerList';
|
||||
import JoinServer from './JoinServer/JoinServer';
|
||||
import styles from './LoginPage.module.css';
|
||||
|
||||
const LoginPage = () => {
|
||||
return (
|
||||
<>
|
||||
<div className='login-page-container'>
|
||||
<ServerList />
|
||||
<AddServer />
|
||||
</div>
|
||||
</>
|
||||
<div className={styles.container}>
|
||||
<JoinServer />
|
||||
<AddServer />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
8
src/components/LoginPage/LoginPage.module.css
Normal file
8
src/components/LoginPage/LoginPage.module.css
Normal file
@ -0,0 +1,8 @@
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
height: 50%;
|
||||
width: 100%;
|
||||
}
|
||||
19
src/components/LoginPage/LoginPage.test.js
Normal file
19
src/components/LoginPage/LoginPage.test.js
Normal file
@ -0,0 +1,19 @@
|
||||
import React from 'react';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import '@testing-library/jest-dom';
|
||||
import LoginPage from './LoginPage';
|
||||
|
||||
jest.mock('./JoinServer/JoinServer', () => () => <div data-testid="join-server" />);
|
||||
jest.mock('./AddServer/AddServer', () => () => <div data-testid="add-server" />);
|
||||
|
||||
describe('LoginPage component', () => {
|
||||
it('should renders JoinServer component ', () => {
|
||||
render(<LoginPage />);
|
||||
expect(screen.getByTestId('join-server')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should renders AddServer component', () => {
|
||||
render(<LoginPage />);
|
||||
expect(screen.getByTestId('add-server')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@ -1,59 +0,0 @@
|
||||
.name-input-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;
|
||||
}
|
||||
.name-input-container > button {
|
||||
margin-top: 5px;
|
||||
text-align: center;
|
||||
width: 100px;
|
||||
align-self: center;
|
||||
}
|
||||
.name-input-container > input {
|
||||
margin-top: 10px;
|
||||
}
|
||||
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: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);
|
||||
}
|
||||
@ -1,8 +1,8 @@
|
||||
import React, { useState, useContext, useEffect, useCallback } from 'react';
|
||||
import { SocketContext } from '../../../App';
|
||||
import useInput from '../../../hooks/useInput';
|
||||
import './NameInput.css';
|
||||
import useKeyPress from '../../../hooks/useKeyPress';
|
||||
import styles from './NameInput.module.css';
|
||||
|
||||
const NameInput = ({ isRoomPrivate, roomId }) => {
|
||||
const socket = useContext(SocketContext);
|
||||
@ -10,11 +10,12 @@ const NameInput = ({ isRoomPrivate, roomId }) => {
|
||||
const password = useInput('');
|
||||
const [isPasswordWrong, setIsPasswordWrong] = useState(false);
|
||||
|
||||
const handleButtonClick = useCallback(() => {
|
||||
const handleButtonClick = () => {
|
||||
socket.emit('player:login', { name: nickname.value, password: password.value, roomId: roomId });
|
||||
}, [socket, nickname.value, password.value, roomId]);
|
||||
};
|
||||
|
||||
useKeyPress('Enter', handleButtonClick);
|
||||
|
||||
useEffect(() => {
|
||||
socket.on('error:wrongPassword', () => {
|
||||
setIsPasswordWrong(true);
|
||||
@ -22,13 +23,13 @@ const NameInput = ({ isRoomPrivate, roomId }) => {
|
||||
}, [socket]);
|
||||
|
||||
return (
|
||||
<div className='name-input-container' style={{ height: isRoomPrivate ? '100px' : '50px' }}>
|
||||
<input placeholder='Nickname' type='text' onChange={nickname.onChange} />
|
||||
<div className={styles.container} style={{ height: isRoomPrivate ? '100px' : '50px' }}>
|
||||
<input placeholder='Nickname' type='text' {...nickname} />
|
||||
{isRoomPrivate ? (
|
||||
<input
|
||||
placeholder='Room password'
|
||||
type='text'
|
||||
onChange={password.onChange}
|
||||
{...password}
|
||||
style={{ backgroundColor: isPasswordWrong ? 'red' : null }}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
20
src/components/LoginPage/NameInput/NameInput.module.css
Normal file
20
src/components/LoginPage/NameInput/NameInput.module.css
Normal file
@ -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;
|
||||
}
|
||||
89
src/components/LoginPage/NameInput/NameInput.test.js
Normal file
89
src/components/LoginPage/NameInput/NameInput.test.js
Normal file
@ -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(
|
||||
<SocketContext.Provider value={mockSocket}>
|
||||
<NameInput isRoomPrivate={true} />
|
||||
</SocketContext.Provider>
|
||||
);
|
||||
expect(screen.getByPlaceholderText('Room password')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not renders password field when room is not private', () => {
|
||||
render(
|
||||
<SocketContext.Provider value={mockSocket}>
|
||||
<NameInput isRoomPrivate={false} />
|
||||
</SocketContext.Provider>
|
||||
);
|
||||
expect(screen.queryByPlaceholderText('Room password')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should handles input change', () => {
|
||||
render(
|
||||
<SocketContext.Provider value={mockSocket}>
|
||||
<NameInput isRoomPrivate={false} />
|
||||
</SocketContext.Provider>
|
||||
);
|
||||
const nicknameInput = screen.getByPlaceholderText('Nickname');
|
||||
fireEvent.change(nicknameInput, { target: { value: 'TestName' } });
|
||||
expect(nicknameInput.value).toBe('TestName');
|
||||
});
|
||||
|
||||
it('should handles password change', () => {
|
||||
render(
|
||||
<SocketContext.Provider value={mockSocket}>
|
||||
<NameInput isRoomPrivate={true} />
|
||||
</SocketContext.Provider>
|
||||
);
|
||||
const passwordInput = screen.getByPlaceholderText('Room password');
|
||||
fireEvent.change(passwordInput, { target: { value: 'TestPassword' } });
|
||||
expect(passwordInput.value).toBe('TestPassword');
|
||||
});
|
||||
|
||||
it('should handles button click', () => {
|
||||
render(
|
||||
<SocketContext.Provider value={mockSocket}>
|
||||
<NameInput isRoomPrivate={true} roomId={123} />
|
||||
</SocketContext.Provider>
|
||||
);
|
||||
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(
|
||||
<SocketContext.Provider value={mockSocket}>
|
||||
<NameInput isRoomPrivate={true} roomId={123} />
|
||||
</SocketContext.Provider>
|
||||
);
|
||||
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,
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -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;
|
||||
}
|
||||
@ -1,82 +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';
|
||||
import Overlay from '../../Overlay/Overlay';
|
||||
|
||||
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 (
|
||||
<div className='lp-container'>
|
||||
<div className='title-container'>
|
||||
<h1>Server List</h1>
|
||||
<div className='refresh'>
|
||||
<img src={refresh} alt='refresh' onClick={getRooms}></img>
|
||||
</div>
|
||||
</div>
|
||||
<div className='server-container content-container'>
|
||||
{rooms ? (
|
||||
<table className='rooms'>
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>Server</th>
|
||||
<th>#/#</th>
|
||||
<th>Status</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{rooms.map((room, index) => (
|
||||
<tr key={index}>
|
||||
<td>{room.private ? <img src={lock} alt='private' /> : null}</td>
|
||||
<td className='room-name'>{room.name}</td>
|
||||
<td>{`${room.players.length}/4`}</td>
|
||||
<td>{room.isStarted ? 'started' : 'waiting'}</td>
|
||||
<td>
|
||||
<button onClick={() => handleJoinClick(room)}>Join</button>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
) : (
|
||||
<div style={{ alignSelf: 'center' }}>
|
||||
<ReactLoading type='spinningBubbles' color='white' height={50} width={50} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{joining ? (
|
||||
<Overlay handleOverlayClose={() => setJoining(false)}>
|
||||
<NameInput roomId={clickedRoom._id} isRoomPrivate={clickedRoom.private} />
|
||||
</Overlay>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default ServerList;
|
||||
15
src/components/LoginPage/WindowLayout/WindowLayout.jsx
Normal file
15
src/components/LoginPage/WindowLayout/WindowLayout.jsx
Normal file
@ -0,0 +1,15 @@
|
||||
import styles from './WindowLayout.module.css';
|
||||
|
||||
const WindowLayout = ({ title, titleComponent, content }) => {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.title}>
|
||||
<h1>{title}</h1>
|
||||
{titleComponent}
|
||||
</div>
|
||||
<div className={styles.content}>{content}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default WindowLayout;
|
||||
@ -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;
|
||||
28
src/components/LoginPage/WindowLayout/WindowLayout.test.js
Normal file
28
src/components/LoginPage/WindowLayout/WindowLayout.test.js
Normal file
@ -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 }) => (
|
||||
<div data-testid='mocked-window-layout'>
|
||||
<div data-testid='mocked-title'>{title}</div>
|
||||
<div data-testid='mocked-title-component'>{titleComponent}</div>
|
||||
<div data-testid='mocked-content'>{content}</div>
|
||||
</div>
|
||||
));
|
||||
|
||||
describe('WindowLayout component', () => {
|
||||
it('should render without crashing', () => {
|
||||
render(
|
||||
<WindowLayout
|
||||
title='Test Title'
|
||||
titleComponent={<div>Test Title Component</div>}
|
||||
content={<div>Test Content</div>}
|
||||
/>
|
||||
);
|
||||
expect(screen.getByTestId('mocked-window-layout')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('mocked-title')).toHaveTextContent('Test Title');
|
||||
expect(screen.getByTestId('mocked-title-component')).toHaveTextContent('Test Title Component');
|
||||
expect(screen.getByTestId('mocked-content')).toHaveTextContent('Test Content');
|
||||
});
|
||||
});
|
||||
@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
@ -11,5 +11,5 @@ export default function useKeyPress(targetKey, callback) {
|
||||
return () => {
|
||||
window.removeEventListener('keydown', keyPressHandler);
|
||||
};
|
||||
}, []);
|
||||
}, [keyPressHandler]);
|
||||
}
|
||||
|
||||
@ -4,8 +4,14 @@ import { SocketContext } from '../App';
|
||||
const useSocketData = port => {
|
||||
const socket = useContext(SocketContext);
|
||||
const [data, setData] = useState(null);
|
||||
socket.on(port, data => {
|
||||
setData(data);
|
||||
socket.on(port, res => {
|
||||
let parsedData;
|
||||
try {
|
||||
parsedData = JSON.parse(res);
|
||||
} catch (error) {
|
||||
parsedData = res;
|
||||
}
|
||||
setData(parsedData);
|
||||
});
|
||||
return [data, setData];
|
||||
};
|
||||
|
||||
@ -67,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;
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user