Мокирование дат

Компоненты календаря относятся к числу наиболее чувствительных к текущей дате и времени. Любая зависимость от системного времени приводит к нестабильности тестов: поведение изменяется от дня к дню, от часового пояса к часовому поясу, а также при запуске в CI-средах.

В календарных UI-библиотеках, таких как Pikaday, текущее время используется для вычисления:

  • подсветки «сегодня»
  • доступности дат относительно minDate/maxDate
  • начального отображаемого месяца
  • состояния выбранной даты при инициализации
  • логики отключения выходных или прошедших дней

Отсутствие контроля над источником времени делает невозможным воспроизводимое тестирование, поэтому мокирование дат становится обязательной частью стратегии проверки календарной логики.

Источники времени в JavaScript и их влияние на тестируемость

В JavaScript существует несколько способов получения текущего времени:

Date.now()
new Date()
new Date().getTime()

Все эти способы в конечном итоге зависят от системных часов среды выполнения. Это создаёт ряд проблем:

  • невозможность зафиксировать «сегодня» в тестах
  • различие поведения между локальной машиной и CI
  • нестабильность тестов при переходе через полночь
  • различия в таймзонах

Pikaday использует Date для вычисления текущего месяца и выделения актуального дня, поэтому любые изменения системного времени напрямую влияют на рендер.

Поведение Pikaday, зависящее от текущей даты

Внутри календарного компонента текущая дата влияет на несколько ключевых аспектов:

  • вычисление класса для «today»
  • проверка границ minDate и maxDate
  • автопереход к текущему месяцу при инициализации
  • подсветка выбранного дня, если defaultDate не задан
  • логика отображения отключённых дат

Особенно чувствительной является логика сравнения дат без времени:

if (isSameDay(date, new Date())) {
  // выделение текущего дня
}

Любое смещение системного времени делает такие проверки непредсказуемыми.

Принцип мокирования системного времени

Мокирование времени заключается в подмене глобального источника даты на фиксированное значение. Это позволяет стабилизировать:

  • «сегодня»
  • текущее время
  • вычисление месяцев и годов
  • поведение относительных дат

Основные стратегии:

  • переопределение Date
  • использование fake timers
  • установка фиксированной таймзоны

Переопределение Date вручную

Простейший способ контроля времени — замена глобального конструктора:

const REAL_DATE = Date;

global.Date = class extends Date {
  constructor(...args) {
    if (args.length) {
      return new REAL_DATE(...args);
    }
    return new REAL_DATE('2024-06-15T12:00:00Z');
  }

  static now() {
    return new REAL_DATE('2024-06-15T12:00:00Z').getTime();
  }
};

Такой подход обеспечивает стабильное «сегодня», но имеет ограничения:

  • сложность восстановления оригинального поведения
  • возможные конфликты с библиотеками, использующими Date нестандартно
  • трудности при параллельных тестах

Использование fake timers в Jest

Более устойчивый подход реализуется через системные таймеры тестового фреймворка:

jest.useFakeTimers();
jest.setSystemTime(new Date('2024-06-15T12:00:00Z'));

После фиксации времени Pikaday начинает работать в полностью детерминированной среде.

Ключевые эффекты:

  • new Date() возвращает зафиксированное значение
  • Date.now() стабилен
  • все вычисления календаря повторяемы

Дополнительно:

jest.useRealTimers();

восстанавливает реальное время после теста.

Fake timers в Sinon

При использовании Sinon применяется аналогичный механизм:

import sinon from 'sinon';

const clock = sinon.useFakeTimers(new Date('2024-06-15T12:00:00Z'));

// тестирование Pikaday

clock.restore();

Sinon предоставляет более тонкий контроль:

  • перемотка времени вперёд
  • имитация перехода дней
  • управление таймерами анимации

Таймзоны и влияние окружения

Даже при фиксации даты остаётся проблема часового пояса. Pikaday оперирует локальными датами браузера, поэтому окружение влияет на итоговый результат.

Для стабилизации применяются:

Установка TZ в Node.js

TZ=UTC

или

process.env.TZ = 'UTC';

Это устраняет расхождения при сравнении дат.

Проблема локальных смещений

При сравнении:

  • 2024-06-15T00:00:00Z
  • локального времени браузера

может возникать сдвиг на сутки, что критично для логики «today».

Особенности мокирования в Pikaday

Pikaday не хранит внутренний источник времени, а всегда опирается на Date. Это создаёт прямую зависимость от глобального окружения.

Особенно важно учитывать:

  • перерисовка календаря при изменении месяца
  • вычисление массива дней месяца
  • формирование сетки недель

Пример чувствительного участка:

var today = new Date();
var isToday = day.getDate() === today.getDate() &&
              day.getMonth() === today.getMonth() &&
              day.getFullYear() === today.getFullYear();

При отсутствии мокирования такие проверки нестабильны.

Стратегия фиксации «сегодня» в тестах Pikaday

Фиксированное время выбирается с учётом границ месяца:

  • середина месяца для тестирования сетки дней
  • начало месяца для проверки переходов
  • конец года для проверки декабрь/январь логики

Пример стабилизированной среды:

jest.useFakeTimers();
jest.setSystemTime(new Date('2024-03-10T10:00:00Z'));

Это позволяет воспроизводить календарную сетку без вариаций.

Тестирование minDate и maxDate при фиксированном времени

Мокирование времени критично при проверке ограничений:

new Pikaday({
  field: document.querySelector('input'),
  minDate: new Date('2024-03-05'),
  maxDate: new Date('2024-03-20')
});

При нестабильном системном времени возможны ошибки:

  • неправильное выделение доступных дат
  • смещение активного месяца
  • некорректная блокировка элементов

Фиксация времени устраняет зависимость от текущей даты среды.

Интеграция моков с DOM-тестами

При тестировании Pikaday через DOM (например, JSDOM) важно учитывать:

  • отсутствие реального layout-движка
  • упрощённую модель даты
  • особенности рендеринга календарной сетки

Комбинация:

  • fake timers
  • фиксированный TZ
  • стабильный DOM

создаёт полностью воспроизводимую среду для проверки календарной логики.

Проблемные сценарии без мокирования

Отсутствие фиксации времени приводит к следующим классам ошибок:

  • тесты проходят локально, но падают в CI
  • смещение «today» на один день при смене часового пояса
  • нестабильные snapshot-тесты календаря
  • некорректное поведение при запуске в полночь

Особенно критично это проявляется при проверке UI-снимков календаря, где даже один день смещения меняет всю структуру сетки.

Изоляция Pikaday от системного времени

На практике применяется дополнительный уровень абстракции — обёртка над инициализацией:

function createCalendar(root, dateProvider) {
  return new Pikaday({
    field: root,
    defaultDate: dateProvider(),
    setDefaultDate: true
  });
}

При тестировании dateProvider заменяется на фиксированную функцию:

() => new Date('2024-06-15T00:00:00Z')

Это снижает зависимость от глобальных моков и упрощает контроль поведения.

Стабильность вычислений в календарной сетке

Календарные алгоритмы Pikaday строятся вокруг:

  • определения первого дня недели
  • расчёта смещения начала месяца
  • построения массива из 28–31 дней
  • добавления соседних месяцев

Любое изменение «сегодня» влияет на:

  • стартовую позицию календаря
  • активный месяц
  • выделение выбранного дня

Фиксированное время обеспечивает неизменность входных данных для этих вычислений.

Поведение при переходе через границу суток

Особый случай возникает при тестировании около полуночи:

  • до 00:00 один календарный день
  • после 00:00 следующий день

Без мокирования тесты могут демонстрировать разные результаты в зависимости от времени запуска.

Фиксация системного времени полностью устраняет этот класс нестабильности.