Аудит тестов выявил критические пробелы в покрытии. Расширили существующие файлы и добавили два новых: Новые файлы: - src/main/validate.test.ts (59) — security-boundary IPC layer вообще не имел тестов. Покрывает NaN/Infinity, range edge cases, тип- сабверсии, partial-patch semantics, quietHours regex+dedup. Фиксирует контракт «strict для required, lenient для optional defaults» (input принимает enabled:'yes' → true, patch строгий). - src/renderer/src/lib/icon-choices.test.ts (3) — SAMPLE_EXERCISES.icon ⊆ ICON_CHOICES (иначе fallback-Activity на первом запуске). Расширения: - format.test.ts: NaN/Infinity guard, EN-локаль. - history.test.ts: DST-safe инвариант (unique keys, monotonic), plannedRepsToday, future-dated entries, mixed actions. - i18n.test.ts: dict parity RU↔EN (с правильным skip для RU-only *_few CLDR-категории), regex-injection в var-значениях, weekday.short.* parity. Рефакторинг: - ICON_CHOICES вынесен в src/renderer/src/lib/icon-choices.ts (без JSX) — теперь whitelist импортируется из любого слоя без React-зависимости. icon.tsx реэкспортирует для обратной совместимости.
86 lines
3.1 KiB
TypeScript
86 lines
3.1 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
||
import { formatCountdown, formatInterval } from './format'
|
||
|
||
describe('formatCountdown', () => {
|
||
it('returns "сейчас" for zero or negative ms', () => {
|
||
expect(formatCountdown(0)).toBe('сейчас')
|
||
expect(formatCountdown(-1)).toBe('сейчас')
|
||
expect(formatCountdown(-100_000)).toBe('сейчас')
|
||
})
|
||
|
||
it('renders sub-minute as seconds only', () => {
|
||
expect(formatCountdown(1_000)).toBe('1с')
|
||
expect(formatCountdown(45_000)).toBe('45с')
|
||
expect(formatCountdown(59_999)).toBe('59с')
|
||
})
|
||
|
||
it('renders minutes with zero-padded seconds', () => {
|
||
expect(formatCountdown(60_000)).toBe('1м 00с')
|
||
expect(formatCountdown(65_000)).toBe('1м 05с')
|
||
expect(formatCountdown(125_000)).toBe('2м 05с')
|
||
expect(formatCountdown(599_000)).toBe('9м 59с')
|
||
})
|
||
|
||
it('renders hours with zero-padded minutes and drops seconds', () => {
|
||
expect(formatCountdown(3_600_000)).toBe('1ч 00м')
|
||
expect(formatCountdown(3_660_000)).toBe('1ч 01м')
|
||
expect(formatCountdown(7_245_000)).toBe('2ч 00м')
|
||
expect(formatCountdown(7_320_000)).toBe('2ч 02м')
|
||
})
|
||
|
||
it('floors fractional seconds (no rounding up)', () => {
|
||
// 999ms > 0 so not "сейчас"; Math.floor(999/1000) = 0 → "0с"
|
||
expect(formatCountdown(999)).toBe('0с')
|
||
expect(formatCountdown(500)).toBe('0с')
|
||
})
|
||
|
||
// Guard added in v0.5.2 — electron-updater и scheduler могут передать
|
||
// NaN/Infinity на ранних событиях. Должны вернуть «сейчас», не «NaNс».
|
||
it('returns "сейчас" for NaN and Infinity (defensive guard)', () => {
|
||
expect(formatCountdown(NaN)).toBe('сейчас')
|
||
expect(formatCountdown(Infinity)).toBe('сейчас')
|
||
expect(formatCountdown(-Infinity)).toBe('сейчас')
|
||
})
|
||
|
||
describe('english locale', () => {
|
||
it('renders sub-minute with "s"', () => {
|
||
expect(formatCountdown(45_000, 'en')).toBe('45s')
|
||
})
|
||
it('renders minutes+seconds with "m"/"s"', () => {
|
||
expect(formatCountdown(65_000, 'en')).toBe('1m 05s')
|
||
})
|
||
it('renders hours+minutes with "h"/"m"', () => {
|
||
expect(formatCountdown(3_660_000, 'en')).toBe('1h 01m')
|
||
})
|
||
it('returns "now" for zero', () => {
|
||
expect(formatCountdown(0, 'en')).toBe('now')
|
||
})
|
||
})
|
||
})
|
||
|
||
describe('formatInterval', () => {
|
||
it('renders minutes under an hour', () => {
|
||
expect(formatInterval(1)).toBe('1 мин')
|
||
expect(formatInterval(30)).toBe('30 мин')
|
||
expect(formatInterval(59)).toBe('59 мин')
|
||
})
|
||
|
||
it('renders whole hours without minute remainder', () => {
|
||
expect(formatInterval(60)).toBe('1 ч')
|
||
expect(formatInterval(120)).toBe('2 ч')
|
||
expect(formatInterval(180)).toBe('3 ч')
|
||
})
|
||
|
||
it('renders mixed hours+minutes', () => {
|
||
expect(formatInterval(61)).toBe('1 ч 1 мин')
|
||
expect(formatInterval(90)).toBe('1 ч 30 мин')
|
||
expect(formatInterval(125)).toBe('2 ч 5 мин')
|
||
})
|
||
|
||
it('english locale', () => {
|
||
expect(formatInterval(30, 'en')).toBe('30 min')
|
||
expect(formatInterval(60, 'en')).toBe('1 h')
|
||
expect(formatInterval(90, 'en')).toBe('1 h 30 min')
|
||
})
|
||
})
|