Files
annu-kute-ced/tests/js/countdown-test.js
T

191 lines
6.8 KiB
JavaScript

/**
* Tests unitaires pour js/countdown.js (classe CountdownTimer).
*
* Le script est chargé dans un contexte vm avec un DOM simulé :
* la date courante est figée (FakeDate) et setInterval est factice,
* ce qui rend les calculs de temps restant déterministes.
*/
'use strict';
const { describe, test } = require('node:test');
const assert = require('node:assert');
const fs = require('node:fs');
const path = require('node:path');
const vm = require('node:vm');
const COUNTDOWN_PATH = path.join(__dirname, '..', '..', 'js', 'countdown.js');
const COUNTDOWN_SOURCE = fs.readFileSync(COUNTDOWN_PATH, 'utf8');
// Date courante figée pour tous les tests
const FIXED_NOW = new Date('2030-01-01T00:00:00Z').getTime();
function makeElements() {
return {
'countdown-days': { textContent: '' },
'countdown-hours': { textContent: '' },
'countdown-minutes': { textContent: '' },
'countdown-seconds': { textContent: '' }
};
}
/**
* Charge countdown.js dans un contexte vm isolé et retourne la classe.
*
* @param {object|null} options.elements Éléments DOM simulés (null = absents)
* @param {number} options.now Timestamp courant simulé
*/
function loadCountdown({ elements = null, now = FIXED_NOW } = {}) {
class FakeDate extends Date {
constructor(...args) {
if (args.length === 0) {
super(now);
} else {
super(...args);
}
}
static now() {
return now;
}
}
const windowMock = {
addEventListener() {},
location: { href: '' }
};
// Timers factices : le timer ne tourne jamais tout seul,
// on enregistre simplement les appels pour vérification.
const timers = { intervals: [], cleared: [] };
let nextIntervalId = 1;
const sandbox = {
document: {
getElementById: (id) => (elements ? elements[id] || null : null),
querySelectorAll: () => [],
addEventListener() {}
},
window: windowMock,
Date: FakeDate,
setInterval: (fn, delay) => {
const id = nextIntervalId++;
timers.intervals.push({ id, fn, delay });
return id;
},
clearInterval: (id) => {
timers.cleared.push(id);
},
setTimeout: () => 0,
console
};
vm.createContext(sandbox);
const CountdownTimer = vm.runInContext(COUNTDOWN_SOURCE + '\nCountdownTimer;', sandbox);
return { CountdownTimer, window: windowMock, timers };
}
describe('CountdownTimer', () => {
test('formatNumber complète les nombres sur deux chiffres', () => {
const { CountdownTimer } = loadCountdown();
assert.strictEqual(CountdownTimer.prototype.formatNumber(0), '00');
assert.strictEqual(CountdownTimer.prototype.formatNumber(5), '05');
assert.strictEqual(CountdownTimer.prototype.formatNumber(42), '42');
assert.strictEqual(CountdownTimer.prototype.formatNumber(123), '123');
});
test('calcule correctement le temps restant (jours/heures/minutes/secondes)', () => {
const distance =
2 * 24 * 60 * 60 * 1000 + // 2 jours
3 * 60 * 60 * 1000 + // 3 heures
4 * 60 * 1000 + // 4 minutes
5 * 1000; // 5 secondes
const elements = makeElements();
const { CountdownTimer } = loadCountdown({ elements });
const timer = new CountdownTimer(FIXED_NOW + distance);
try {
assert.strictEqual(elements['countdown-days'].textContent, '02');
assert.strictEqual(elements['countdown-hours'].textContent, '03');
assert.strictEqual(elements['countdown-minutes'].textContent, '04');
assert.strictEqual(elements['countdown-seconds'].textContent, '05');
} finally {
timer.stop();
}
});
test('reporte les heures au-delà de 24h dans les jours', () => {
const distance = 36 * 60 * 60 * 1000; // 36 heures = 1 jour + 12 heures
const elements = makeElements();
const { CountdownTimer } = loadCountdown({ elements });
const timer = new CountdownTimer(FIXED_NOW + distance);
try {
assert.strictEqual(elements['countdown-days'].textContent, '01');
assert.strictEqual(elements['countdown-hours'].textContent, '12');
assert.strictEqual(elements['countdown-minutes'].textContent, '00');
assert.strictEqual(elements['countdown-seconds'].textContent, '00');
} finally {
timer.stop();
}
});
test('une date passée arrête le compte à rebours et redirige vers /', () => {
const elements = makeElements();
const { CountdownTimer, window } = loadCountdown({ elements });
const timer = new CountdownTimer(FIXED_NOW - 1000);
try {
// onComplete() redirige vers la page principale
assert.strictEqual(window.location.href, '/');
// Les éléments ne sont pas mis à jour quand le compte est terminé
assert.strictEqual(elements['countdown-days'].textContent, '');
assert.strictEqual(elements['countdown-hours'].textContent, '');
assert.strictEqual(elements['countdown-minutes'].textContent, '');
assert.strictEqual(elements['countdown-seconds'].textContent, '');
} finally {
timer.stop();
}
});
test('une distance nulle affiche zéro partout sans rediriger', () => {
const elements = makeElements();
const { CountdownTimer, window } = loadCountdown({ elements });
const timer = new CountdownTimer(FIXED_NOW);
try {
assert.strictEqual(elements['countdown-days'].textContent, '00');
assert.strictEqual(elements['countdown-hours'].textContent, '00');
assert.strictEqual(elements['countdown-minutes'].textContent, '00');
assert.strictEqual(elements['countdown-seconds'].textContent, '00');
assert.strictEqual(window.location.href, '');
} finally {
timer.stop();
}
});
test('fonctionne sans éléments DOM présents', () => {
const { CountdownTimer } = loadCountdown({ elements: null });
const timer = new CountdownTimer(FIXED_NOW + 60 * 1000);
// Aucune erreur attendue malgré l'absence des éléments
timer.stop();
});
test('démarre un intervalle d\'une seconde et stop() le nettoie', () => {
const { CountdownTimer, timers } = loadCountdown();
const timer = new CountdownTimer(FIXED_NOW + 60 * 1000);
assert.strictEqual(timers.intervals.length, 1);
assert.strictEqual(timers.intervals[0].delay, 1000);
assert.strictEqual(timers.cleared.length, 0);
timer.stop();
assert.deepStrictEqual(timers.cleared, [timer.interval]);
});
});