fix: ISO countdown date, IntlDateFormatter and UTF-8 truncation
This commit is contained in:
@@ -80,11 +80,17 @@ function loadCountdown({ elements = null, now = FIXED_NOW } = {}) {
|
||||
};
|
||||
|
||||
vm.createContext(sandbox);
|
||||
const CountdownTimer = vm.runInContext(COUNTDOWN_SOURCE + '\nCountdownTimer;', sandbox);
|
||||
const { CountdownTimer, parseTargetDate } = vm.runInContext(
|
||||
COUNTDOWN_SOURCE + '\n({ CountdownTimer, parseTargetDate });',
|
||||
sandbox
|
||||
);
|
||||
|
||||
return { CountdownTimer, window: windowMock, timers };
|
||||
return { CountdownTimer, parseTargetDate, window: windowMock, timers };
|
||||
}
|
||||
|
||||
// Convertit un timestamp en chaîne ISO 8601, comme le fait PHP via DateTime::ATOM
|
||||
const toISO = (timestamp) => new Date(timestamp).toISOString();
|
||||
|
||||
describe('CountdownTimer', () => {
|
||||
test('formatNumber complète les nombres sur deux chiffres', () => {
|
||||
const { CountdownTimer } = loadCountdown();
|
||||
@@ -104,7 +110,7 @@ describe('CountdownTimer', () => {
|
||||
|
||||
const elements = makeElements();
|
||||
const { CountdownTimer } = loadCountdown({ elements });
|
||||
const timer = new CountdownTimer(FIXED_NOW + distance);
|
||||
const timer = new CountdownTimer(toISO(FIXED_NOW + distance));
|
||||
|
||||
try {
|
||||
assert.strictEqual(elements['countdown-days'].textContent, '02');
|
||||
@@ -121,7 +127,7 @@ describe('CountdownTimer', () => {
|
||||
|
||||
const elements = makeElements();
|
||||
const { CountdownTimer } = loadCountdown({ elements });
|
||||
const timer = new CountdownTimer(FIXED_NOW + distance);
|
||||
const timer = new CountdownTimer(toISO(FIXED_NOW + distance));
|
||||
|
||||
try {
|
||||
assert.strictEqual(elements['countdown-days'].textContent, '01');
|
||||
@@ -136,7 +142,7 @@ describe('CountdownTimer', () => {
|
||||
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);
|
||||
const timer = new CountdownTimer(toISO(FIXED_NOW - 1000));
|
||||
|
||||
try {
|
||||
// onComplete() redirige vers la page principale
|
||||
@@ -155,7 +161,7 @@ describe('CountdownTimer', () => {
|
||||
test('une distance nulle affiche zéro partout sans rediriger', () => {
|
||||
const elements = makeElements();
|
||||
const { CountdownTimer, window } = loadCountdown({ elements });
|
||||
const timer = new CountdownTimer(FIXED_NOW);
|
||||
const timer = new CountdownTimer(toISO(FIXED_NOW));
|
||||
|
||||
try {
|
||||
assert.strictEqual(elements['countdown-days'].textContent, '00');
|
||||
@@ -170,7 +176,7 @@ describe('CountdownTimer', () => {
|
||||
|
||||
test('fonctionne sans éléments DOM présents', () => {
|
||||
const { CountdownTimer } = loadCountdown({ elements: null });
|
||||
const timer = new CountdownTimer(FIXED_NOW + 60 * 1000);
|
||||
const timer = new CountdownTimer(toISO(FIXED_NOW + 60 * 1000));
|
||||
|
||||
// Aucune erreur attendue malgré l'absence des éléments
|
||||
timer.stop();
|
||||
@@ -178,7 +184,7 @@ describe('CountdownTimer', () => {
|
||||
|
||||
test('démarre un intervalle d\'une seconde et stop() le nettoie', () => {
|
||||
const { CountdownTimer, timers } = loadCountdown();
|
||||
const timer = new CountdownTimer(FIXED_NOW + 60 * 1000);
|
||||
const timer = new CountdownTimer(toISO(FIXED_NOW + 60 * 1000));
|
||||
|
||||
assert.strictEqual(timers.intervals.length, 1);
|
||||
assert.strictEqual(timers.intervals[0].delay, 1000);
|
||||
@@ -187,4 +193,78 @@ describe('CountdownTimer', () => {
|
||||
timer.stop();
|
||||
assert.deepStrictEqual(timers.cleared, [timer.interval]);
|
||||
});
|
||||
|
||||
test('accepte une date ISO 8601 avec fuseau horaire (contrat PHP)', () => {
|
||||
// FIXED_NOW = 2030-01-01T00:00:00Z
|
||||
// Cible : 2030-01-02T05:00:00+04:00, soit 2030-01-02T01:00:00Z → 1 jour + 1 heure
|
||||
const elements = makeElements();
|
||||
const { CountdownTimer } = loadCountdown({ elements });
|
||||
const timer = new CountdownTimer('2030-01-02T05:00:00+04:00');
|
||||
|
||||
try {
|
||||
assert.strictEqual(elements['countdown-days'].textContent, '01');
|
||||
assert.strictEqual(elements['countdown-hours'].textContent, '01');
|
||||
assert.strictEqual(elements['countdown-minutes'].textContent, '00');
|
||||
assert.strictEqual(elements['countdown-seconds'].textContent, '00');
|
||||
} finally {
|
||||
timer.stop();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseTargetDate', () => {
|
||||
test('analyse une date ISO 8601 avec décalage horaire', () => {
|
||||
const { parseTargetDate } = loadCountdown();
|
||||
|
||||
assert.strictEqual(
|
||||
parseTargetDate('2025-10-11T00:00:00-04:00'),
|
||||
Date.UTC(2025, 9, 11, 4, 0, 0)
|
||||
);
|
||||
assert.strictEqual(
|
||||
parseTargetDate('2025-10-11T00:00:00+02:00'),
|
||||
Date.UTC(2025, 9, 10, 22, 0, 0)
|
||||
);
|
||||
});
|
||||
|
||||
test('analyse une date ISO 8601 suffixée Z', () => {
|
||||
const { parseTargetDate } = loadCountdown();
|
||||
|
||||
assert.strictEqual(
|
||||
parseTargetDate('2025-10-11T00:00:00Z'),
|
||||
Date.UTC(2025, 9, 11, 0, 0, 0)
|
||||
);
|
||||
});
|
||||
|
||||
test('accepte le format historique avec espace (cas Safari)', () => {
|
||||
const { parseTargetDate } = loadCountdown();
|
||||
|
||||
// Sans fuseau explicite, la date est interprétée en UTC
|
||||
assert.strictEqual(
|
||||
parseTargetDate('2025-10-11 00:00:00'),
|
||||
Date.UTC(2025, 9, 11, 0, 0, 0)
|
||||
);
|
||||
});
|
||||
|
||||
test('accepte les millisecondes et les décalages sans deux-points', () => {
|
||||
const { parseTargetDate } = loadCountdown();
|
||||
|
||||
assert.strictEqual(parseTargetDate('2030-01-01T00:00:00.000Z'), FIXED_NOW);
|
||||
assert.strictEqual(
|
||||
parseTargetDate('2025-10-11T00:00:00-0400'),
|
||||
Date.UTC(2025, 9, 11, 4, 0, 0)
|
||||
);
|
||||
});
|
||||
|
||||
test('accepte un timestamp numérique tel quel', () => {
|
||||
const { parseTargetDate } = loadCountdown();
|
||||
|
||||
assert.strictEqual(parseTargetDate(FIXED_NOW), FIXED_NOW);
|
||||
});
|
||||
|
||||
test('retourne le même instant que new Date pour une date ISO valide', () => {
|
||||
const { parseTargetDate } = loadCountdown();
|
||||
const iso = '2030-06-15T12:30:45+00:00';
|
||||
|
||||
assert.strictEqual(parseTargetDate(iso), new Date(iso).getTime());
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
/**
|
||||
* Tests unitaires pour les fonctions de formatage de includes/config.php
|
||||
* Tests unitaires pour les fonctions de formatage de includes/lib/format.php
|
||||
* (formatDuration, formatViewCount, formatDate, formatVideosData)
|
||||
* et truncateText de includes/structured-data.php
|
||||
*/
|
||||
@@ -210,3 +210,39 @@ assertEquals(
|
||||
truncateText($textNoSpace, 200),
|
||||
'truncateText coupe à la limite quand il n\'y a pas d\'espace'
|
||||
);
|
||||
|
||||
// --- truncateText : UTF-8 multioctet (mb_substr) -----------------------------
|
||||
|
||||
$utf8WithSpace = str_repeat('é', 150) . ' ' . str_repeat('b', 100); // 251 caractères
|
||||
assertEquals(
|
||||
str_repeat('é', 150) . '...',
|
||||
truncateText($utf8WithSpace, 200),
|
||||
'truncateText compte les caractères UTF-8 et non les octets'
|
||||
);
|
||||
|
||||
$utf8NoSpace = str_repeat('€', 250); // 3 octets par caractère
|
||||
$truncatedUtf8 = truncateText($utf8NoSpace, 200);
|
||||
assertEquals(
|
||||
str_repeat('€', 200) . '...',
|
||||
$truncatedUtf8,
|
||||
'truncateText ne coupe pas un caractère multioctet en deux'
|
||||
);
|
||||
assertEquals(1, preg_match('//u', $truncatedUtf8), 'truncateText retourne de l\'UTF-8 valide');
|
||||
|
||||
// --- formatDateFr (includes/structured-data.php) -----------------------------
|
||||
|
||||
$dateFr = new DateTime('2025-10-11 00:00:00', new DateTimeZone('Indian/Reunion'));
|
||||
assertEquals('11 octobre 2025', formatDateFr($dateFr), 'formatDateFr formate la date longue en français');
|
||||
assertEquals(
|
||||
'11 octobre 2025 à 00:00',
|
||||
formatDateFr($dateFr, true),
|
||||
'formatDateFr ajoute l\'heure quand demandé'
|
||||
);
|
||||
|
||||
$dateFrSummer = new DateTime('2026-07-04 12:30:00', new DateTimeZone('UTC'));
|
||||
assertEquals('4 juillet 2026', formatDateFr($dateFrSummer), 'formatDateFr sans zéro initial sur le jour');
|
||||
assertEquals(
|
||||
'4 juillet 2026 à 12:30',
|
||||
formatDateFr($dateFrSummer, true),
|
||||
'formatDateFr utilise le fuseau de la date fournie'
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user