191 lines
6.8 KiB
JavaScript
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]);
|
||
|
|
});
|
||
|
|
});
|