mirror of
https://github.com/langgenius/dify.git
synced 2026-02-24 18:05:11 +00:00
367 lines
11 KiB
TypeScript
367 lines
11 KiB
TypeScript
import dayjs, {
|
|
clearMonthMapCache,
|
|
cloneTime,
|
|
formatDateForOutput,
|
|
getDateWithTimezone,
|
|
getDaysInMonth,
|
|
getHourIn12Hour,
|
|
parseDateWithFormat,
|
|
toDayjs,
|
|
} from './dayjs'
|
|
|
|
describe('dayjs extended utilities', () => {
|
|
// Tests for cloneTime
|
|
describe('cloneTime', () => {
|
|
it('should copy hour and minute from source to target', () => {
|
|
const target = dayjs('2024-01-15')
|
|
const source = dayjs('2024-06-20 14:30')
|
|
|
|
const result = cloneTime(target, source)
|
|
|
|
expect(result.hour()).toBe(14)
|
|
expect(result.minute()).toBe(30)
|
|
expect(result.date()).toBe(15)
|
|
expect(result.month()).toBe(0) // January
|
|
})
|
|
|
|
it('should not mutate the original target date', () => {
|
|
const target = dayjs('2024-01-15 08:00')
|
|
const source = dayjs('2024-06-20 14:30')
|
|
|
|
cloneTime(target, source)
|
|
|
|
expect(target.hour()).toBe(8)
|
|
expect(target.minute()).toBe(0)
|
|
})
|
|
})
|
|
|
|
// Tests for getDaysInMonth
|
|
describe('getDaysInMonth', () => {
|
|
beforeEach(() => {
|
|
clearMonthMapCache()
|
|
})
|
|
|
|
it('should return an array of Day objects', () => {
|
|
const date = dayjs('2024-06-15')
|
|
const days = getDaysInMonth(date)
|
|
|
|
expect(days.length).toBeGreaterThan(0)
|
|
days.forEach((day) => {
|
|
expect(day).toHaveProperty('date')
|
|
expect(day).toHaveProperty('isCurrentMonth')
|
|
})
|
|
})
|
|
|
|
it('should include days from previous and next month to fill the grid', () => {
|
|
const date = dayjs('2024-06-15') // June 2024 starts on Saturday
|
|
const days = getDaysInMonth(date)
|
|
|
|
const prevMonthDays = days.filter(d => !d.isCurrentMonth && d.date.month() < date.month())
|
|
const nextMonthDays = days.filter(d => !d.isCurrentMonth && d.date.month() > date.month())
|
|
|
|
// June 2024 starts on Saturday (6), so there are 6 days from previous month
|
|
expect(prevMonthDays.length).toBeGreaterThan(0)
|
|
expect(nextMonthDays.length).toBeGreaterThan(0)
|
|
})
|
|
|
|
it('should mark current month days correctly', () => {
|
|
const date = dayjs('2024-06-15')
|
|
const days = getDaysInMonth(date)
|
|
|
|
const currentMonthDays = days.filter(d => d.isCurrentMonth)
|
|
// June has 30 days
|
|
expect(currentMonthDays).toHaveLength(30)
|
|
})
|
|
|
|
it('should cache results for the same month', () => {
|
|
const date1 = dayjs('2024-06-15')
|
|
const date2 = dayjs('2024-06-20')
|
|
|
|
const days1 = getDaysInMonth(date1)
|
|
const days2 = getDaysInMonth(date2)
|
|
|
|
// Same reference since it's cached
|
|
expect(days1).toBe(days2)
|
|
})
|
|
|
|
it('should return different results for different months', () => {
|
|
const june = dayjs('2024-06-15')
|
|
const july = dayjs('2024-07-15')
|
|
|
|
const juneDays = getDaysInMonth(june)
|
|
const julyDays = getDaysInMonth(july)
|
|
|
|
expect(juneDays).not.toBe(julyDays)
|
|
})
|
|
})
|
|
|
|
// Tests for clearMonthMapCache
|
|
describe('clearMonthMapCache', () => {
|
|
it('should clear the cache so new days are generated', () => {
|
|
const date = dayjs('2024-06-15')
|
|
|
|
const days1 = getDaysInMonth(date)
|
|
clearMonthMapCache()
|
|
const days2 = getDaysInMonth(date)
|
|
|
|
// After clearing cache, a new array should be created
|
|
expect(days1).not.toBe(days2)
|
|
// But should have the same length
|
|
expect(days1.length).toBe(days2.length)
|
|
})
|
|
})
|
|
|
|
// Tests for getHourIn12Hour
|
|
describe('getHourIn12Hour', () => {
|
|
it('should return 12 for midnight (hour 0)', () => {
|
|
const date = dayjs('2024-01-01 00:00')
|
|
expect(getHourIn12Hour(date)).toBe(12)
|
|
})
|
|
|
|
it('should return hour as-is for 1-11 AM', () => {
|
|
expect(getHourIn12Hour(dayjs('2024-01-01 01:00'))).toBe(1)
|
|
expect(getHourIn12Hour(dayjs('2024-01-01 11:00'))).toBe(11)
|
|
})
|
|
|
|
it('should return 0 for noon (hour 12)', () => {
|
|
const date = dayjs('2024-01-01 12:00')
|
|
expect(getHourIn12Hour(date)).toBe(0)
|
|
})
|
|
|
|
it('should return hour - 12 for PM hours (13-23)', () => {
|
|
expect(getHourIn12Hour(dayjs('2024-01-01 13:00'))).toBe(1)
|
|
expect(getHourIn12Hour(dayjs('2024-01-01 23:00'))).toBe(11)
|
|
})
|
|
})
|
|
|
|
// Tests for getDateWithTimezone
|
|
describe('getDateWithTimezone', () => {
|
|
it('should return a cloned date when no timezone is provided', () => {
|
|
const date = dayjs('2024-06-15')
|
|
const result = getDateWithTimezone({ date })
|
|
|
|
expect(result.format('YYYY-MM-DD')).toBe('2024-06-15')
|
|
})
|
|
|
|
it('should return current date clone when neither date nor timezone is provided', () => {
|
|
const result = getDateWithTimezone({})
|
|
const now = dayjs()
|
|
|
|
expect(result.format('YYYY-MM-DD')).toBe(now.format('YYYY-MM-DD'))
|
|
})
|
|
|
|
it('should apply timezone to provided date', () => {
|
|
const date = dayjs('2024-06-15T12:00:00')
|
|
const result = getDateWithTimezone({ date, timezone: 'America/New_York' })
|
|
|
|
// dayjs.tz converts the date to the given timezone
|
|
expect(result).toBeDefined()
|
|
expect(result.isValid()).toBe(true)
|
|
})
|
|
|
|
it('should return current time in timezone when only timezone is provided', () => {
|
|
const result = getDateWithTimezone({ timezone: 'Asia/Tokyo' })
|
|
|
|
expect(result.utcOffset()).toBe(dayjs().tz('Asia/Tokyo').utcOffset())
|
|
})
|
|
})
|
|
|
|
// Tests for toDayjs additional edge cases
|
|
describe('toDayjs edge cases', () => {
|
|
it('should return undefined for empty string', () => {
|
|
expect(toDayjs('')).toBeUndefined()
|
|
})
|
|
|
|
it('should return undefined for undefined', () => {
|
|
expect(toDayjs(undefined)).toBeUndefined()
|
|
})
|
|
|
|
it('should handle Dayjs object input', () => {
|
|
const date = dayjs('2024-06-15')
|
|
const result = toDayjs(date)
|
|
|
|
expect(result).toBeDefined()
|
|
expect(result?.format('YYYY-MM-DD')).toBe('2024-06-15')
|
|
})
|
|
|
|
it('should handle Dayjs object with timezone', () => {
|
|
const date = dayjs('2024-06-15T12:00:00')
|
|
const result = toDayjs(date, { timezone: 'UTC' })
|
|
|
|
expect(result).toBeDefined()
|
|
})
|
|
|
|
it('should parse with custom format when format matches common formats', () => {
|
|
// Uses a format from COMMON_PARSE_FORMATS
|
|
const result = toDayjs('2024-06-15', { format: 'YYYY-MM-DD' })
|
|
|
|
expect(result).toBeDefined()
|
|
expect(result?.format('YYYY-MM-DD')).toBe('2024-06-15')
|
|
})
|
|
|
|
it('should fall back when custom format does not match', () => {
|
|
// dayjs strict mode with format requires customParseFormat plugin
|
|
// which is not loaded, so invalid format falls through to other parsing
|
|
const result = toDayjs('2024-06-15', { format: 'INVALID', timezone: 'UTC' })
|
|
|
|
// It will still be parsed by fallback mechanisms
|
|
expect(result).toBeDefined()
|
|
})
|
|
|
|
it('should parse time with seconds', () => {
|
|
const result = toDayjs('14:30:45', { timezone: 'UTC' })
|
|
|
|
expect(result).toBeDefined()
|
|
expect(result?.hour()).toBe(14)
|
|
expect(result?.minute()).toBe(30)
|
|
expect(result?.second()).toBe(45)
|
|
})
|
|
|
|
it('should parse time with milliseconds', () => {
|
|
const result = toDayjs('14:30:45.123', { timezone: 'UTC' })
|
|
|
|
expect(result).toBeDefined()
|
|
expect(result?.millisecond()).toBe(123)
|
|
})
|
|
|
|
it('should normalize short milliseconds by padding', () => {
|
|
const result = toDayjs('14:30:45.1', { timezone: 'UTC' })
|
|
|
|
expect(result).toBeDefined()
|
|
expect(result?.millisecond()).toBe(100)
|
|
})
|
|
|
|
it('should truncate long milliseconds to 3 digits', () => {
|
|
// The time regex only captures up to 3 digits for ms, so 4+ digit values
|
|
// don't match the regex and fall through to common format parsing
|
|
const result = toDayjs('14:30:45.12', { timezone: 'UTC' })
|
|
|
|
expect(result).toBeDefined()
|
|
// 2-digit ms "12" gets padded to "120"
|
|
expect(result?.millisecond()).toBe(120)
|
|
})
|
|
|
|
it('should parse 12-hour AM time', () => {
|
|
const result = toDayjs('07:15 AM', { timezone: 'UTC' })
|
|
|
|
expect(result).toBeDefined()
|
|
expect(result?.hour()).toBe(7)
|
|
expect(result?.minute()).toBe(15)
|
|
})
|
|
|
|
it('should parse 12-hour time with seconds', () => {
|
|
const result = toDayjs('07:15:30 PM', { timezone: 'UTC' })
|
|
|
|
expect(result).toBeDefined()
|
|
expect(result?.hour()).toBe(19)
|
|
expect(result?.second()).toBe(30)
|
|
})
|
|
|
|
it('should handle 12 PM correctly', () => {
|
|
const result = toDayjs('12:00 PM', { timezone: 'UTC' })
|
|
|
|
expect(result).toBeDefined()
|
|
expect(result?.hour()).toBe(12)
|
|
})
|
|
|
|
it('should handle 12 AM correctly', () => {
|
|
const result = toDayjs('12:00 AM', { timezone: 'UTC' })
|
|
|
|
expect(result).toBeDefined()
|
|
expect(result?.hour()).toBe(0)
|
|
})
|
|
|
|
it('should use custom formats array when provided', () => {
|
|
const result = toDayjs('2024.06.15', { formats: ['YYYY.MM.DD'] })
|
|
|
|
expect(result).toBeDefined()
|
|
expect(result?.format('YYYY-MM-DD')).toBe('2024-06-15')
|
|
})
|
|
|
|
it('should fall back to native parsing for ISO strings', () => {
|
|
const result = toDayjs('2024-06-15T12:00:00.000Z')
|
|
|
|
expect(result).toBeDefined()
|
|
})
|
|
|
|
it('should return undefined for completely unparseable value', () => {
|
|
const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
|
|
const result = toDayjs('not-a-date')
|
|
|
|
expect(result).toBeUndefined()
|
|
consoleSpy.mockRestore()
|
|
})
|
|
})
|
|
|
|
// Tests for parseDateWithFormat
|
|
describe('parseDateWithFormat', () => {
|
|
it('should return null for empty string', () => {
|
|
expect(parseDateWithFormat('')).toBeNull()
|
|
})
|
|
|
|
it('should parse with provided format from common formats', () => {
|
|
// Uses YYYY-MM-DD which is in COMMON_PARSE_FORMATS
|
|
const result = parseDateWithFormat('2024-06-15', 'YYYY-MM-DD')
|
|
|
|
expect(result).not.toBeNull()
|
|
expect(result?.format('YYYY-MM-DD')).toBe('2024-06-15')
|
|
})
|
|
|
|
it('should return null for invalid date with format', () => {
|
|
const result = parseDateWithFormat('not-a-date', 'YYYY-MM-DD')
|
|
|
|
expect(result).toBeNull()
|
|
})
|
|
|
|
it('should try common formats when no format is specified', () => {
|
|
const result = parseDateWithFormat('2024-06-15')
|
|
|
|
expect(result).not.toBeNull()
|
|
expect(result?.format('YYYY-MM-DD')).toBe('2024-06-15')
|
|
})
|
|
|
|
it('should parse ISO datetime format', () => {
|
|
const result = parseDateWithFormat('2024-06-15T12:00:00')
|
|
|
|
expect(result).not.toBeNull()
|
|
})
|
|
|
|
it('should return null for unparseable string without format', () => {
|
|
const result = parseDateWithFormat('gibberish')
|
|
|
|
expect(result).toBeNull()
|
|
})
|
|
})
|
|
|
|
// Tests for formatDateForOutput
|
|
describe('formatDateForOutput', () => {
|
|
it('should return empty string for invalid date', () => {
|
|
const invalidDate = dayjs('invalid')
|
|
expect(formatDateForOutput(invalidDate)).toBe('')
|
|
})
|
|
|
|
it('should format date-only output without time', () => {
|
|
const date = dayjs('2024-06-15T12:00:00')
|
|
const result = formatDateForOutput(date)
|
|
|
|
expect(result).toBe('2024-06-15')
|
|
})
|
|
|
|
it('should format with time when includeTime is true', () => {
|
|
const date = dayjs('2024-06-15T12:00:00')
|
|
const result = formatDateForOutput(date, true)
|
|
|
|
expect(result).toContain('2024-06-15')
|
|
expect(result).toContain('12:00:00')
|
|
})
|
|
|
|
it('should default to date-only format', () => {
|
|
const date = dayjs('2024-06-15T14:30:00')
|
|
const result = formatDateForOutput(date)
|
|
|
|
expect(result).toBe('2024-06-15')
|
|
expect(result).not.toContain('14:30')
|
|
})
|
|
})
|
|
})
|