end to end tests for the game page

This commit is contained in:
AlecM33
2022-09-02 15:53:15 -04:00
parent a891251ac7
commit b05afb85ae
32 changed files with 1276 additions and 1585 deletions

View File

@@ -0,0 +1,170 @@
import { globals } from '../../config/globals.js';
export class GameTimerManager {
constructor (stateBucket, socket) {
this.stateBucket = stateBucket;
this.playListener = () => {
socket.emit(globals.SOCKET_EVENTS.IN_GAME_MESSAGE, globals.EVENT_IDS.RESUME_TIMER, this.stateBucket.currentGameState.accessCode);
};
this.pauseListener = () => {
socket.emit(globals.SOCKET_EVENTS.IN_GAME_MESSAGE, globals.EVENT_IDS.PAUSE_TIMER, this.stateBucket.currentGameState.accessCode);
};
}
resumeGameTimer (totalTime, tickRate, soundManager, timerWorker) {
if (window.Worker) {
if (
this.stateBucket.currentGameState.client.userType === globals.USER_TYPES.MODERATOR
|| this.stateBucket.currentGameState.client.userType === globals.USER_TYPES.TEMPORARY_MODERATOR
) {
this.swapToPauseButton();
}
const instance = this;
const timer = document.getElementById('game-timer');
timer.classList.remove('paused');
timer.classList.remove('paused-low');
timer.classList.remove('low');
if (totalTime < 60000) {
timer.classList.add('low');
}
timer.innerText = totalTime < 60000
? returnHumanReadableTime(totalTime, true)
: returnHumanReadableTime(totalTime);
timerWorker.onmessage = function (e) {
if (e.data.hasOwnProperty('timeRemainingInMilliseconds') && e.data.timeRemainingInMilliseconds >= 0) {
if (e.data.timeRemainingInMilliseconds === 0) {
instance.displayExpiredTime();
} else if (e.data.timeRemainingInMilliseconds < 60000) {
timer.classList.add('low');
timer.innerText = e.data.displayTime;
} else {
timer.innerText = e.data.displayTime;
}
}
};
timerWorker.postMessage({ totalTime: totalTime, tickInterval: tickRate });
}
}
pauseGameTimer (timerWorker, timeRemaining) {
if (window.Worker) {
if (
this.stateBucket.currentGameState.client.userType === globals.USER_TYPES.MODERATOR
|| this.stateBucket.currentGameState.client.userType === globals.USER_TYPES.TEMPORARY_MODERATOR
) {
this.swapToPlayButton();
}
timerWorker.postMessage('stop');
const timer = document.getElementById('game-timer');
if (timeRemaining < 60000) {
timer.innerText = returnHumanReadableTime(timeRemaining, true);
timer.classList.add('paused-low');
timer.classList.add('low');
} else {
timer.innerText = returnHumanReadableTime(timeRemaining);
timer.classList.add('paused');
}
}
}
displayPausedTime (time) {
if (
this.stateBucket.currentGameState.client.userType === globals.USER_TYPES.MODERATOR
|| this.stateBucket.currentGameState.client.userType === globals.USER_TYPES.TEMPORARY_MODERATOR
) {
this.swapToPlayButton();
}
const timer = document.getElementById('game-timer');
if (time < 60000) {
timer.innerText = returnHumanReadableTime(time, true);
timer.classList.add('paused-low');
timer.classList.add('low');
} else {
timer.innerText = returnHumanReadableTime(time);
timer.classList.add('paused');
}
}
displayExpiredTime () {
const currentBtn = document.querySelector('#play-pause img');
if (currentBtn) {
currentBtn.removeEventListener('click', this.pauseListener);
currentBtn.removeEventListener('click', this.playListener);
currentBtn.remove();
}
const timer = document.getElementById('game-timer');
timer.innerText = returnHumanReadableTime(0, true);
}
attachTimerSocketListeners (socket, timerWorker, gameStateRenderer) {
if (!socket.hasListeners(globals.COMMANDS.PAUSE_TIMER)) {
socket.on(globals.COMMANDS.PAUSE_TIMER, (timeRemaining) => {
this.pauseGameTimer(timerWorker, timeRemaining);
});
}
if (!socket.hasListeners(globals.COMMANDS.RESUME_TIMER)) {
socket.on(globals.COMMANDS.RESUME_TIMER, (timeRemaining) => {
this.resumeGameTimer(timeRemaining, globals.CLOCK_TICK_INTERVAL_MILLIS, null, timerWorker);
});
}
if (!socket.hasListeners(globals.COMMANDS.GET_TIME_REMAINING)) {
socket.on(globals.COMMANDS.GET_TIME_REMAINING, (timeRemaining, paused) => {
if (paused) {
this.displayPausedTime(timeRemaining);
} else if (timeRemaining === 0) {
this.displayExpiredTime();
} else {
this.resumeGameTimer(timeRemaining, globals.CLOCK_TICK_INTERVAL_MILLIS, null, timerWorker);
}
});
}
}
swapToPlayButton () {
const currentBtn = document.querySelector('#play-pause img');
if (currentBtn) {
currentBtn.removeEventListener('click', this.pauseListener);
currentBtn.remove();
}
const playBtn = document.createElement('img');
playBtn.setAttribute('src', '../images/play-button.svg');
playBtn.addEventListener('click', this.playListener);
document.querySelector('#play-pause-placeholder')?.remove();
document.getElementById('play-pause').appendChild(playBtn);
}
swapToPauseButton () {
const currentBtn = document.querySelector('#play-pause img');
if (currentBtn) {
currentBtn.removeEventListener('click', this.playListener);
currentBtn.remove();
}
const pauseBtn = document.createElement('img');
pauseBtn.setAttribute('src', '../images/pause-button.svg');
pauseBtn.addEventListener('click', this.pauseListener);
document.querySelector('#play-pause-placeholder')?.remove();
document.getElementById('play-pause').appendChild(pauseBtn);
}
}
function returnHumanReadableTime (milliseconds, tenthsOfSeconds = false) {
const tenths = Math.floor((milliseconds / 100) % 10);
let seconds = Math.floor((milliseconds / 1000) % 60);
let minutes = Math.floor((milliseconds / (1000 * 60)) % 60);
let hours = Math.floor((milliseconds / (1000 * 60 * 60)) % 24);
hours = hours < 10 ? '0' + hours : hours;
minutes = minutes < 10 ? '0' + minutes : minutes;
seconds = seconds < 10 ? '0' + seconds : seconds;
return tenthsOfSeconds
? hours + ':' + minutes + ':' + seconds + '.' + tenths
: hours + ':' + minutes + ':' + seconds;
}

View File

@@ -0,0 +1,122 @@
/*
A timer using setTimeout that compensates for drift. Drift can happen for several reasons:
https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout#reasons_for_delays
This means the timer may very well be late in executing the next call (but never early).
This timer is accurate to within a few ms for any amount of time provided. This is the client-side version of this module,
and is meant to be utilized as a Web Worker.
See: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API
*/
const messageParameters = {
STOP: 'stop',
TICK_INTERVAL: 'tickInterval',
TOTAL_TIME: 'totalTime'
};
let timer;
onmessage = function (e) {
if (typeof e.data === 'object'
&& e.data.hasOwnProperty(messageParameters.TOTAL_TIME)
&& e.data.hasOwnProperty(messageParameters.TICK_INTERVAL)
) {
timer = new Singleton(e.data.totalTime, e.data.tickInterval);
timer.startTimer();
} else if (e.data === 'stop') {
timer.stopTimer();
}
};
function stepFn (expected, interval, start, totalTime) {
const now = Date.now();
if (now - start >= totalTime) {
postMessage({
timeRemainingInMilliseconds: 0,
displayTime: returnHumanReadableTime(0, true)
});
return;
}
const delta = now - expected;
expected += interval;
const displayTime = (totalTime - (now - start)) < 60000
? returnHumanReadableTime(totalTime - (now - start), true)
: returnHumanReadableTime(totalTime - (now - start));
postMessage({
timeRemainingInMilliseconds: totalTime - (now - start),
displayTime: displayTime
});
Singleton.setNewTimeoutReference(setTimeout(() => {
stepFn(expected, interval, start, totalTime);
}, Math.max(0, interval - delta)
)); // take into account drift - also retain a reference to this clock tick so it can be cleared later
}
class Timer {
constructor (totalTime, tickInterval) {
this.timeoutId = undefined;
this.totalTime = totalTime;
this.tickInterval = tickInterval;
}
startTimer () {
if (!isNaN(this.tickInterval)) {
const interval = this.tickInterval;
const start = Date.now();
const expected = Date.now() + this.tickInterval;
const totalTime = this.totalTime;
if (this.timeoutId) {
clearTimeout(this.timeoutId);
}
this.timeoutId = setTimeout(() => {
stepFn(expected, interval, start, totalTime);
}, this.tickInterval);
}
}
stopTimer () {
if (this.timeoutId) {
clearTimeout(this.timeoutId);
}
}
}
class Singleton {
constructor (totalTime, tickInterval) {
if (!Singleton.instance) {
Singleton.instance = new Timer(totalTime, tickInterval);
} else {
// This allows the same timer to be configured to run for different intervals / at a different granularity.
Singleton.setNewTimerParameters(totalTime, tickInterval);
}
return Singleton.instance;
}
static setNewTimerParameters (totalTime, tickInterval) {
if (Singleton.instance) {
Singleton.instance.totalTime = totalTime;
Singleton.instance.tickInterval = tickInterval;
}
}
static setNewTimeoutReference (timeoutId) {
if (Singleton.instance) {
Singleton.instance.timeoutId = timeoutId;
}
}
}
function returnHumanReadableTime (milliseconds, tenthsOfSeconds = false) {
const tenths = Math.floor((milliseconds / 100) % 10);
let seconds = Math.floor((milliseconds / 1000) % 60);
let minutes = Math.floor((milliseconds / (1000 * 60)) % 60);
let hours = Math.floor((milliseconds / (1000 * 60 * 60)) % 24);
hours = hours < 10 ? '0' + hours : hours;
minutes = minutes < 10 ? '0' + minutes : minutes;
seconds = seconds < 10 ? '0' + seconds : seconds;
return tenthsOfSeconds
? hours + ':' + minutes + ':' + seconds + '.' + tenths
: hours + ':' + minutes + ':' + seconds;
}