Тестирование локализации проверяет корректность пользовательских локалей, правильное склонение числительных и соответствие строк ожидаемому формату для каждого временного интервала.
Прежде чем тестировать, нужно знать, что именно проверять. Функция
локали принимает (number, index) и возвращает
[прошлое, будущее]. Индекс соответствует интервалу:
| Index | Интервал |
|---|---|
| 0 | < 45 секунд |
| 1 | 1 секунда |
| 2 | 2-44 секунды |
| 3 | 45-89 секунд |
| 4 | 1-2 минуты |
| 5 | 2-44 минуты |
| 6 | 45-89 минут |
| 7 | 1-2 часа |
| 8 | 2-21 часа |
| 9 | 22-35 часов |
| 10 | 1-2 дня |
| 11 | 2-25 дней |
| 12 | 26-345 дней |
| 13 | 1-2 года |
| 14 | > 2 лет |
import { customRuLocale } from '../src/locales/ru';
describe('customRuLocale', () => {
it('возвращает кортеж [string, string] для каждого индекса', () => {
for (let index = 0; index <= 14; index++) {
const result = customRuLocale(1, index);
expect(result).toHaveLength(2);
expect(typeof result[0]).toBe('string');
expect(typeof result[1]).toBe('string');
}
});
it('не возвращает пустые строки', () => {
for (let index = 0; index <= 14; index++) {
const [past, future] = customRuLocale(1, index);
expect(past.length).toBeGreaterThan(0);
expect(future.length).toBeGreaterThan(0);
}
});
});
import { ruLocale } from '../src/locales/ru';
describe('ruLocale — конкретные формы', () => {
it('индекс 0: "только что"', () => {
expect(ruLocale(1, 0)[0]).toBe('только что');
});
it('индекс 4: "минуту назад"', () => {
expect(ruLocale(1, 4)[0]).toBe('минуту назад');
});
it('индекс 5: "5 минут назад" (число подставляется)', () => {
expect(ruLocale(5, 5)[0]).toBe('5 минут назад');
});
it('индекс 7: "час назад"', () => {
expect(ruLocale(1, 7)[0]).toBe('час назад');
});
it('индекс 13: "год назад"', () => {
expect(ruLocale(1, 13)[0]).toBe('год назад');
});
});
Русский язык имеет три формы множественного числа. Важно проверить пограничные значения:
import { format } from 'timeago.js';
const NOW = new Date('2025-06-01T12:00:00Z').getTime();
beforeEach(() => {
jest.useFakeTimers();
jest.setSystemTime(NOW);
});
afterEach(() => jest.useRealTimers());
const cases: Array<[number, RegExp]> = [
[1 * 60_000, /минуту назад/], // 1 минута
[2 * 60_000, /2 минуты назад/], // 2 минуты
[5 * 60_000, /5 минут назад/], // 5 минут
[11 * 60_000, /11 минут назад/], // 11 минут
[21 * 60_000, /21 минуту назад/], // 21 минута
[1 * 3600_000, /час назад/], // 1 час
[2 * 3600_000, /2 часа назад/], // 2 часа
[5 * 3600_000, /5 часов назад/], // 5 часов
[1 * 86400_000, /день назад/], // 1 день
[2 * 86400_000, /2 дня назад/], // 2 дня
[5 * 86400_000, /5 дней назад/], // 5 дней
];
it.each(cases)('%i мс назад соответствует %s', (ms, pattern) => {
expect(format(new Date(NOW - ms), 'ru')).toMatch(pattern);
});
it('форматирует будущее время', () => {
const future = new Date(NOW + 3600_000); // через час
const result = format(future, 'ru');
expect(result).toMatch(/через|позже/);
});
import { format } from 'timeago.js';
it('поддерживает кастомную точку отсчёта', () => {
const base = new Date('2025-06-01T12:00:00Z');
const target = new Date('2025-06-01T11:00:00Z');
const result = format(target, 'ru', { relativeDate: base });
expect(result).toMatch(/час/);
});
const LOCALES: Array<[string, string, RegExp]> = [
['ru', '2025-05-31T12:00:00Z', /день/],
['en_US', '2025-05-31T12:00:00Z', /day/],
['de', '2025-05-31T12:00:00Z', /Tag/],
['fr', '2025-05-31T12:00:00Z', /jour/],
['ja', '2025-05-31T12:00:00Z', /日/],
['zh_CN', '2025-05-31T12:00:00Z', /天/],
];
describe.each(LOCALES)('locale "%s"', (locale, date, pattern) => {
it(`форматирует "1 день назад" на ${locale}`, () => {
jest.useFakeTimers();
jest.setSystemTime(new Date('2025-06-01T12:00:00Z').getTime());
expect(format(date, locale)).toMatch(pattern);
jest.useRealTimers();
});
});
import { register, format } from 'timeago.js';
const TEST_LOCALE = 'test_custom_' + Date.now();
const forms: [string, string][] = new Array(15).fill(null).map((_, i) =>
[`PAST_${i}`, `FUTURE_${i}`]
);
beforeAll(() => {
register(TEST_LOCALE, (n, i) => forms[i] ?? ['PAST_DEFAULT', 'FUTURE_DEFAULT']);
});
it('использует форму из кастомной локали для index 0', () => {
jest.useFakeTimers();
jest.setSystemTime(Date.now());
const result = format(new Date(), TEST_LOCALE);
expect(result).toBe('PAST_0');
jest.useRealTimers();
});
it('форматирует арабскую локаль', () => {
jest.useFakeTimers();
jest.setSystemTime(new Date('2025-06-01T12:00:00Z').getTime());
const result = format('2025-06-01T11:00:00Z', 'ar');
// Арабский использует правосторонние знаки
expect(typeof result).toBe('string');
expect(result.length).toBeGreaterThan(0);
jest.useRealTimers();
});
| Аспект | Что тестировать |
|---|---|
| Структура LocaleFunc | Все индексы 0-14, кортеж из 2 строк |
| Пустые строки | Ни один элемент не пустой |
| Подстановка числа | %s заменяется на реальное число |
| Прошлое и будущее | Обе ветки работают |
| Граничные значения | 1, 2, 5, 11, 21 для склонений |
| Пересечение с format | Кастомная локаль корректно применяется |