Merge pull request #97 from AlecM33/new-e2e

e2e testing capability
This commit is contained in:
Alec
2022-02-25 21:09:58 -05:00
committed by GitHub
13 changed files with 931 additions and 93 deletions

View File

@@ -1,6 +1,6 @@
import { globals } from '../config/globals.js';
import { toast } from './Toast.js';
import { ModalManager } from './ModalManager';
import { ModalManager } from './ModalManager.js';
export class DeckStateManager {
constructor () {

View File

@@ -3,9 +3,9 @@ import { cancelCurrentToast, toast } from './Toast.js';
import { ModalManager } from './ModalManager.js';
import { XHRUtility } from './XHRUtility.js';
import { globals } from '../config/globals.js';
import { templates } from './Templates.js';
import { defaultCards } from '../config/defaultCards';
import { UserUtility } from './UserUtility';
import { HTMLFragments } from './HTMLFragments.js';
import { defaultCards } from '../config/defaultCards.js';
import { UserUtility } from './UserUtility.js';
export class GameCreationStepManager {
constructor (deckManager) {
@@ -192,7 +192,7 @@ function renderNameStep (containerId, step, game, steps) {
const stepContainer = document.createElement('div');
setAttributes(stepContainer, { id: 'step-' + step, class: 'flex-row-container step' });
stepContainer.innerHTML = templates.ENTER_NAME_STEP;
stepContainer.innerHTML = HTMLFragments.ENTER_NAME_STEP;
document.getElementById(containerId).appendChild(stepContainer);
const nameInput = document.querySelector('#moderator-name');
nameInput.value = game.moderatorName;
@@ -246,9 +246,9 @@ function renderRoleSelectionStep (game, containerId, step, deckManager) {
const stepContainer = document.createElement('div');
setAttributes(stepContainer, { id: 'step-' + step, class: 'flex-row-container-left-align step' });
stepContainer.innerHTML = templates.CREATE_GAME_CUSTOM_ROLES;
stepContainer.innerHTML += templates.CREATE_GAME_DECK_STATUS;
stepContainer.innerHTML += templates.CREATE_GAME_DECK;
stepContainer.innerHTML = HTMLFragments.CREATE_GAME_CUSTOM_ROLES;
stepContainer.innerHTML += HTMLFragments.CREATE_GAME_DECK_STATUS;
stepContainer.innerHTML += HTMLFragments.CREATE_GAME_DECK;
const exportHandler = (e) => {
if (e.type === 'click' || e.code === 'Enter') {
@@ -633,7 +633,7 @@ function addOptionsToList (deckManager, selectEl) {
});
for (let i = 0; i < options.length; i++) {
const optionEl = document.createElement('div');
optionEl.innerHTML = templates.DECK_SELECT_ROLE;
optionEl.innerHTML = HTMLFragments.DECK_SELECT_ROLE;
optionEl.classList.add('deck-select-role');
const alignmentClass = options[i].team === globals.ALIGNMENT.GOOD ? globals.ALIGNMENT.GOOD : globals.ALIGNMENT.EVIL;
optionEl.classList.add(alignmentClass);

View File

@@ -1,6 +1,6 @@
import { globals } from '../config/globals.js';
import { toast } from './Toast.js';
import { templates } from './Templates.js';
import { HTMLFragments } from './HTMLFragments.js';
import { ModalManager } from './ModalManager.js';
export class GameStateRenderer {
@@ -343,9 +343,9 @@ function renderGroupOfPlayers (
container.classList.add('game-player');
if (moderatorType) {
container.dataset.pointer = player.id;
container.innerHTML = templates.MODERATOR_PLAYER;
container.innerHTML = HTMLFragments.MODERATOR_PLAYER;
} else {
container.innerHTML = templates.GAME_PLAYER;
container.innerHTML = HTMLFragments.GAME_PLAYER;
}
container.querySelector('.game-player-name').innerText = player.name;
const roleElement = container.querySelector('.game-player-role');
@@ -492,7 +492,7 @@ function removeExistingPlayerElements (killPlayerHandlers, revealRoleHandlers) {
function createEndGamePromptComponent (socket, stateBucket) {
if (document.querySelector('#end-game-prompt') === null) {
const div = document.createElement('div');
div.innerHTML = templates.END_GAME_PROMPT;
div.innerHTML = HTMLFragments.END_GAME_PROMPT;
div.querySelector('#end-game-button').addEventListener('click', (e) => {
e.preventDefault();
if (confirm('End the game?')) {

View File

@@ -1,4 +1,4 @@
export const templates = {
export const HTMLFragments = {
LOBBY:
`<div id='lobby-header'>
<div>

View File

@@ -1,9 +1,11 @@
import { DeckStateManager } from '../modules/DeckStateManager.js';
import { GameCreationStepManager } from '../modules/GameCreationStepManager.js';
import { injectNavbar } from '../modules/Navbar.js';
import createTemplate from "../view_templates/CreateTemplate.js";
const create = () => {
injectNavbar();
document.getElementById("game-creation-container").innerHTML = createTemplate;
const deckManager = new DeckStateManager();
const gameCreationStepManager = new GameCreationStepManager(deckManager);
gameCreationStepManager.renderStep('creation-step-container', 1);

View File

@@ -1,6 +1,6 @@
import { UserUtility } from '../modules/UserUtility.js';
import { globals } from '../config/globals.js';
import { templates } from '../modules/Templates.js';
import { HTMLFragments } from '../modules/HTMLFragments.js';
import { GameStateRenderer } from '../modules/GameStateRenderer.js';
import { toast } from '../modules/Toast.js';
import { GameTimerManager } from '../modules/GameTimerManager.js';
@@ -54,7 +54,7 @@ function syncWithGame (stateBucket, gameTimerManager, gameStateRenderer, timerWo
stateBucket.currentGameState = gameState;
document.querySelector('.spinner-container')?.remove();
document.querySelector('.spinner-background')?.remove();
document.getElementById('game-content').innerHTML = templates.INITIAL_GAME_DOM;
document.getElementById('game-content').innerHTML = HTMLFragments.INITIAL_GAME_DOM;
toast('You are connected.', 'success', true, true, 2);
processGameState(stateBucket.currentGameState, cookie, socket, gameStateRenderer, gameTimerManager, timerWorker);
}
@@ -72,7 +72,7 @@ function processGameState (currentGameState, userId, socket, gameStateRenderer,
}
switch (currentGameState.status) {
case globals.STATUS.LOBBY:
document.getElementById('game-state-container').innerHTML = templates.LOBBY;
document.getElementById('game-state-container').innerHTML = HTMLFragments.LOBBY;
gameStateRenderer.renderLobbyHeader();
gameStateRenderer.renderLobbyPlayers();
if (
@@ -90,24 +90,24 @@ function processGameState (currentGameState, userId, socket, gameStateRenderer,
gameStateRenderer.renderGameHeader();
switch (currentGameState.client.userType) {
case globals.USER_TYPES.PLAYER:
document.getElementById('game-state-container').innerHTML = templates.PLAYER_GAME_VIEW;
document.getElementById('game-state-container').innerHTML = HTMLFragments.PLAYER_GAME_VIEW;
gameStateRenderer.renderPlayerView();
break;
case globals.USER_TYPES.KILLED_PLAYER:
document.getElementById('game-state-container').innerHTML = templates.PLAYER_GAME_VIEW;
document.getElementById('game-state-container').innerHTML = HTMLFragments.PLAYER_GAME_VIEW;
gameStateRenderer.renderPlayerView(true);
break;
case globals.USER_TYPES.MODERATOR:
document.getElementById('game-state-container').innerHTML = templates.MODERATOR_GAME_VIEW;
document.getElementById('game-state-container').innerHTML = HTMLFragments.MODERATOR_GAME_VIEW;
gameStateRenderer.renderModeratorView();
break;
case globals.USER_TYPES.TEMPORARY_MODERATOR:
document.getElementById('game-state-container').innerHTML = templates.TEMP_MOD_GAME_VIEW;
document.getElementById('game-state-container').innerHTML = HTMLFragments.TEMP_MOD_GAME_VIEW;
gameStateRenderer.renderTempModView();
break;
case globals.USER_TYPES.SPECTATOR:
document.getElementById('game-state-container').innerHTML = templates.SPECTATOR_GAME_VIEW;
document.getElementById('game-state-container').innerHTML = HTMLFragments.SPECTATOR_GAME_VIEW;
gameStateRenderer.renderSpectatorView();
break;
default:
@@ -122,7 +122,7 @@ function processGameState (currentGameState, userId, socket, gameStateRenderer,
break;
case globals.STATUS.ENDED: {
const container = document.getElementById('game-state-container');
container.innerHTML = templates.END_OF_GAME_VIEW;
container.innerHTML = HTMLFragments.END_OF_GAME_VIEW;
container.classList.add('vertical-flex');
gameStateRenderer.renderEndOfGame();
break;
@@ -297,7 +297,7 @@ function setClientSocketHandlers (stateBucket, gameStateRenderer, socket, timerW
function displayStartGamePromptForModerators (gameState, gameStateRenderer) {
const div = document.createElement('div');
div.innerHTML = templates.START_GAME_PROMPT;
div.innerHTML = HTMLFragments.START_GAME_PROMPT;
div.querySelector('#start-game-button').addEventListener('click', gameStateRenderer.startGameHandler);
document.body.appendChild(div);
}
@@ -354,7 +354,7 @@ function activateRoleInfoButton (deck) {
});
document.getElementById('role-info-button').addEventListener('click', (e) => {
e.preventDefault();
document.getElementById('prompt').innerHTML = templates.ROLE_INFO_MODAL;
document.getElementById('prompt').innerHTML = HTMLFragments.ROLE_INFO_MODAL;
const modalContent = document.getElementById('game-role-info-container');
for (const card of deck) {
const roleDiv = document.createElement('div');

View File

@@ -0,0 +1,64 @@
const template =
`<div id="modal-background" class="modal-background" style="display: none"></div>
<div tabindex="-1" id="role-modal" class="modal" style="display: none">
<form id="role-form">
<div>
<label for="role-name">Role Name</label>
<input id="role-name" type="text" autocomplete="off" placeholder="Name your role..." required/>
</div>
<div>
<label for="role-alignment">Role Alignment</label>
<select id="role-alignment" required>
<option value="good">good</option>
<option value="evil">evil</option>
</select>
</div>
<div>
<label for="role-description">Description</label>
<textarea style="resize:none" id="role-description" rows="4" cols="1" placeholder="Describe your role..." required></textarea>
</div>
<div class="modal-button-container">
<button id="close-modal-button" class="cancel app-button">Close</button>
<input type="submit" id="create-role-button" value="Create Role"/>
</div>
</form>
</div>
<div tabindex="-1" id="upload-custom-roles-modal" class="modal" style="display:none">
<h3>Import Custom Roles</h3>
<form id="upload-custom-roles-form">
<input type="file" id="upload-custom-roles" name="Upload Custom Roles" accept="text/plain"/>
<div class="modal-button-container">
<button id="close-upload-custom-roles-modal-button" class="cancel app-button">Close</button>
<input type="submit" id="upload-custom-roles-button" value="Upload"/>
</div>
</form>
</div>
<div tabindex="-1" id="custom-role-info-modal" class="modal" style="display:none">
<h3 id="custom-role-info-modal-name"></h3>
<div id="custom-role-info-modal-alignment"></div>
<div id="custom-role-info-modal-description"></div>
<div class="modal-button-container">
<button id="close-custom-role-info-modal-button" class="cancel app-button">Close</button>
</div>
</div>
<h1>Create A Game</h1>
<div id="tracker-container">
<div id="creation-step-tracker">
<div id="tracker-step-1" class="creation-step creation-step-filled"></div>
<div id="tracker-step-2" class="creation-step"></div>
<div id="tracker-step-3" class="creation-step"></div>
<div id="tracker-step-4" class="creation-step"></div>
<div id="tracker-step-5" class="creation-step"></div>
</div>
</div>
<div id="creation-step-container">
<h2 id="step-title">Select your method of moderation:</h2>
<div class="placeholder-row">
<div class="animated-placeholder animated-placeholder-short"></div>
<div class="animated-placeholder animated-placeholder-short animated-placeholder-invisible"></div>
</div>
<div class="animated-placeholder animated-placeholder-long"></div>
<div class="animated-placeholder animated-placeholder-long"></div>
</div>`
export default template;

View File

@@ -21,69 +21,7 @@
<body>
<div id="mobile-menu-background-overlay"></div>
<div id="navbar"></div>
<div id="game-creation-container" class="container">
<div id="modal-background" class="modal-background" style="display: none"></div>
<div tabindex="-1" id="role-modal" class="modal" style="display: none">
<form id="role-form">
<div>
<label for="role-name">Role Name</label>
<input id="role-name" type="text" autocomplete="off" placeholder="Name your role..." required/>
</div>
<div>
<label for="role-alignment">Role Alignment</label>
<select id="role-alignment" required>
<option value="good">good</option>
<option value="evil">evil</option>
</select>
</div>
<div>
<label for="role-description">Description</label>
<textarea style="resize:none" id="role-description" rows="4" cols="1" placeholder="Describe your role..." required></textarea>
</div>
<div class="modal-button-container">
<button id="close-modal-button" class="cancel app-button">Close</button>
<input type="submit" id="create-role-button" value="Create Role"/>
</div>
</form>
</div>
<div tabindex="-1" id="upload-custom-roles-modal" class="modal" style="display:none">
<h3>Import Custom Roles</h3>
<form id="upload-custom-roles-form">
<input type="file" id="upload-custom-roles" name="Upload Custom Roles" accept="text/plain"/>
<div class="modal-button-container">
<button id="close-upload-custom-roles-modal-button" class="cancel app-button">Close</button>
<input type="submit" id="upload-custom-roles-button" value="Upload"/>
</div>
</form>
</div>
<div tabindex="-1" id="custom-role-info-modal" class="modal" style="display:none">
<h3 id="custom-role-info-modal-name"></h3>
<div id="custom-role-info-modal-alignment"></div>
<div id="custom-role-info-modal-description"></div>
<div class="modal-button-container">
<button id="close-custom-role-info-modal-button" class="cancel app-button">Close</button>
</div>
</div>
<h1>Create A Game</h1>
<div id="tracker-container">
<div id="creation-step-tracker">
<div id="tracker-step-1" class="creation-step creation-step-filled"></div>
<div id="tracker-step-2" class="creation-step"></div>
<div id="tracker-step-3" class="creation-step"></div>
<div id="tracker-step-4" class="creation-step"></div>
<div id="tracker-step-5" class="creation-step"></div>
</div>
</div>
<div id="creation-step-container">
<h2 id="step-title">Select your method of moderation:</h2>
<div class="placeholder-row">
<div class="animated-placeholder animated-placeholder-short"></div>
<div class="animated-placeholder animated-placeholder-short animated-placeholder-invisible"></div>
</div>
<div class="animated-placeholder animated-placeholder-long"></div>
<div class="animated-placeholder animated-placeholder-long"></div>
</div>
</div>
<div id="game-creation-container" class="container"></div>
<script src="/dist/create-bundle.js"></script>
</body>
</html>

24
karma.conf.js Normal file
View File

@@ -0,0 +1,24 @@
module.exports = function(config) {
config.set({
basePath: './',
frameworks: ['jasmine'],
files: [
{ pattern: 'spec/e2e/*.js', type: 'module' },
{ pattern: 'client/src/modules/*.js', type: 'module', included: true, served: true },
{ pattern: 'client/src/config/*.js', type: 'module', included: true, served: true },
{ pattern: 'client/src/model/*.js', type: 'module', included: true, served: true },
{ pattern: 'client/src/view_templates/*.js', type: 'module', included: true, served: true }
],
reporters: ['progress'],
port: 9876
colors: true,
logLevel: config.LOG_INFO,
browsers: ['ChromeHeadless'],
autoWatch: false,
concurrency: Infinity,
plugins: [
'karma-jasmine',
'karma-chrome-launcher'
]
})
}

692
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -14,7 +14,8 @@
"start:dev:windows:no-hot-reload": "SET NODE_ENV=development && node server/main.js",
"start": "NODE_ENV=production node server/main.js -- loglevel=trace port=8080",
"start:windows": "SET NODE_ENV=production && node server/main.js -- loglevel=warn port=8080",
"test": "jasmine"
"test:unit": "jasmine",
"test:e2e": "karma start --single-run --browsers ChromeHeadless karma.conf.js"
},
"engines": {
"node": ">=14.0.0"
@@ -34,6 +35,7 @@
"express-force-https": "^1.0.0",
"express-rate-limit": "^6.0.1",
"jasmine": "^3.5.0",
"karma-jasmine": "^4.0.1",
"open": "^7.0.3",
"socket.io": "^4.4.0",
"socket.io-client": "^4.4.0",
@@ -47,7 +49,11 @@
"eslint": "^8.6.0",
"eslint-plugin-import": "^2.25.4",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^6.0.0"
"eslint-plugin-promise": "^6.0.0",
"jasmine-browser-runner": "^1.0.0",
"jasmine-core": "^4.0.1",
"karma": "^6.3.16",
"karma-chrome-launcher": "^3.1.0"
},
"nodemonConfig": {
"ignore": [

97
spec/e2e/create_spec.js Normal file
View File

@@ -0,0 +1,97 @@
// TODO: clean up these deep relative paths? jsconfig.json is not working...
import { GameCreationStepManager } from '../../client/src/modules/GameCreationStepManager.js';
import { DeckStateManager } from '../../client/src/modules/DeckStateManager.js';
import createTemplate from '../../client/src/view_templates/CreateTemplate.js';
describe('Create page', function () {
let gameCreationStepManager;
beforeAll(function () {
spyOn(window, 'confirm').and.returnValue(true);
const container = document.createElement('div');
container.setAttribute('id', 'game-creation-container');
document.body.appendChild(container);
document.getElementById('game-creation-container').innerHTML = createTemplate;
const deckManager = new DeckStateManager();
gameCreationStepManager = new GameCreationStepManager(deckManager);
gameCreationStepManager.renderStep('creation-step-container', 1);
});
beforeEach(function () {});
describe('deck builder page', function () {
beforeAll(function () {
document.getElementById('moderation-dedicated').click();
document.getElementById('step-forward-button').click();
});
beforeEach(function () {
document.querySelectorAll('.deck-select-role').forEach((roleEl) => {
roleEl
.querySelector('.deck-select-role-options')
.querySelector('.deck-select-remove')
.click();
});
});
it('should increment a widget when the plus button is clicked and have it show up in the included cards', () => {
const card = gameCreationStepManager.deckManager.getCurrentDeck()[0];
const widget = document.getElementById('card-' + card.role.replaceAll(' ', '-'));
const plusElement = widget.querySelector('.compact-card-right');
plusElement.click();
expect(card.quantity).toEqual(1);
expect(document.getElementsByClassName('deck-role').length).toEqual(1);
});
it('should decrement a widget when the minus button is clicked and remove the role from the included cards', () => {
const card = gameCreationStepManager.deckManager.getCurrentDeck()[0];
card.quantity = 1;
const widget = document.getElementById('card-' + card.role.replaceAll(' ', '-'));
const plusElement = widget.querySelector('.compact-card-left');
plusElement.click();
expect(card.quantity).toEqual(0);
expect(document.getElementsByClassName('deck-role').length).toEqual(0);
});
it('should create a role and display it in the custom role box', () => {
document.getElementById('custom-role-btn').click();
document.getElementById('role-name').value = 'Test name';
document.getElementById('role-description').value = 'Test description';
document.getElementById('create-role-button').click();
expect(document.getElementsByClassName('deck-select-role').length).toEqual(1);
expect(
document.getElementsByClassName('deck-select-role')[0]
.querySelector('.deck-select-role-name').innerText
)
.toEqual('Test name');
});
it('should successfully update role information', () => {
document.getElementById('custom-role-btn').click();
document.getElementById('role-name').value = 'Test name';
document.getElementById('role-description').value = 'Test description';
document.getElementById('create-role-button').click();
document.getElementsByClassName('deck-select-role')[0]
.querySelector('.deck-select-role-options')
.querySelector('.deck-select-edit')
.click();
document.getElementById('role-name').value = 'Test name edited';
document.getElementById('create-role-button').click();
expect(document.getElementsByClassName('deck-select-role').length).toEqual(1);
expect(
document.getElementsByClassName('deck-select-role')[0]
.querySelector('.deck-select-role-name').innerText
)
.toEqual('Test name edited');
expect(gameCreationStepManager.deckManager.getCustomRoleOption(
document.getElementsByClassName('deck-select-role')[0]
.querySelector('.deck-select-role-name').innerText
).role).toEqual('Test name edited');
});
});
});

View File

@@ -0,0 +1,21 @@
{
"srcDir": "src",
"srcFiles": [
"**/*.js"
],
"specDir": "spec/e2e",
"specFiles": [
"**/*[sS]pec.js"
],
"helpers": [
"helpers/**/*.js"
],
"env": {
"stopSpecOnExpectationFailure": false,
"stopOnSpecFailure": false,
"random": true
},
"browser": {
"name": "headlessChrome"
}
}