/** * Tests for cfc-frontend utils/format.js * * Tests the price/rate/score formatting utilities. * These are pure functions - no uni mock needed. */ describe('format.js', () => { let format beforeAll(() => { format = require('@/utils/format') }) // --------------- formatPrice --------------- describe('formatPrice', () => { test('converts cents to yuan with 2 decimals', () => { expect(format.formatPrice(1250)).toBe('12.50') expect(format.formatPrice(0)).toBe('0.00') expect(format.formatPrice(999)).toBe('9.99') expect(format.formatPrice(10000)).toBe('100.00') }) test('handles null and undefined', () => { expect(format.formatPrice(null)).toBeNull() expect(format.formatPrice(undefined)).toBeNull() }) test('handles zero cents', () => { expect(format.formatPrice(0)).toBe('0.00') }) }) // --------------- formatPriceWithSymbol --------------- describe('formatPriceWithSymbol', () => { test('adds ¥ prefix and 2 decimals', () => { expect(format.formatPriceWithSymbol(1250)).toBe('¥12.50') expect(format.formatPriceWithSymbol(0)).toBe('¥0.00') expect(format.formatPriceWithSymbol(999)).toBe('¥9.99') }) test('returns login message for null/undefined', () => { expect(format.formatPriceWithSymbol(null)).toBe('登录查看') expect(format.formatPriceWithSymbol(undefined)).toBe('登录查看') }) }) // --------------- formatRate --------------- describe('formatRate', () => { test('converts permillage to percentage string', () => { expect(format.formatRate(235)).toBe('23.5%') expect(format.formatRate(0)).toBe('0%') expect(format.formatRate(1000)).toBe('100%') expect(format.formatRate(5)).toBe('0.5%') }) test('returns -- for null/undefined', () => { expect(format.formatRate(null)).toBe('--') expect(format.formatRate(undefined)).toBe('--') }) }) // --------------- formatScore --------------- describe('formatScore', () => { test('converts percent-scale to fixed decimal', () => { expect(format.formatScore(450)).toBe('4.5') expect(format.formatScore(0)).toBe('0.0') expect(format.formatScore(1000)).toBe('10.0') }) test('uses default 1 decimal place', () => { expect(format.formatScore(450)).toBe('4.5') }) test('supports custom decimal places', () => { expect(format.formatScore(450, 2)).toBe('4.50') expect(format.formatScore(450, 0)).toBe('5') }) test('returns -- for null/undefined', () => { expect(format.formatScore(null)).toBe('--') expect(format.formatScore(undefined)).toBe('--') }) }) // --------------- parseDate --------------- describe('parseDate', () => { // 断言使用本地时间字段(getFullYear/getMonth/getDate/...), // 避免 Node(V8)对纯日期字符串按 UTC 解析带来的时区差异。 const ymd = (d) => { return d.getFullYear() + '-' + (d.getMonth() + 1) + '-' + d.getDate() } test('parses MySQL DATETIME with space separator "yyyy-MM-dd HH:mm"', () => { // iOS 原生 new Date() 无法解析空格分隔格式,parseDate 必须归一化成功 const d = format.parseDate('2026-08-22 14:00') expect(d).not.toBeNull() expect(ymd(d)).toBe('2026-8-22') expect(d.getHours()).toBe(14) expect(d.getMinutes()).toBe(0) }) test('parses MySQL DATETIME with seconds', () => { const d = format.parseDate('2026-08-22 14:00:30') expect(d).not.toBeNull() expect(ymd(d)).toBe('2026-8-22') expect(d.getSeconds()).toBe(30) }) test('parses Java "EEE MMM dd HH:mm:ss zzz yyyy" (CST) format', () => { // 后端 Java 常见格式,iOS 原生无法解析 const d = format.parseDate('Sat Aug 22 14:00:00 CST 2026') expect(d).not.toBeNull() expect(ymd(d)).toBe('2026-8-22') expect(d.getHours()).toBe(14) }) test('parses slash-separated "yyyy/MM/dd"', () => { const d = format.parseDate('2026/08/22') expect(d).not.toBeNull() expect(ymd(d)).toBe('2026-8-22') }) test('parses ISO 8601 "yyyy-MM-ddTHH:mm:ss"', () => { const d = format.parseDate('2026-08-22T14:00:00') expect(d).not.toBeNull() expect(ymd(d)).toBe('2026-8-22') }) test('parses plain date "yyyy-MM-dd"', () => { const d = format.parseDate('2026-08-22') expect(d).not.toBeNull() expect(ymd(d)).toBe('2026-8-22') }) test('parses numeric timestamp', () => { const d = format.parseDate(1780000000000) expect(d).not.toBeNull() expect(d.getTime()).toBe(1780000000000) }) test('returns same Date object when given a Date', () => { const date = new Date() expect(format.parseDate(date)).toBe(date) }) test('returns null for null/undefined/empty string', () => { expect(format.parseDate(null)).toBeNull() expect(format.parseDate(undefined)).toBeNull() expect(format.parseDate('')).toBeNull() }) test('returns null for unparseable string', () => { expect(format.parseDate('not-a-date')).toBeNull() expect(format.parseDate('2026-13-45')).toBeNull() }) }) // --------------- recordDateOf --------------- // 业务日期字段优先:判定"今日是否打卡"应看业务日期(recordDate/startTime/sleepTime/checkinDate), // 而非 createdAt。createdAt 是记录写入时间(补打时=今天,业务日期=补打日),且后端 Jackson 输出的 // createdAt 形如 "2026-10-30T13:13:32.000+08:00"(带毫秒+时区偏移),iOS JavaScriptCore 无法解析 // (parseDate 注释:只支持不带毫秒的 yyyy-MM-ddTHH:mm:ss+HH:mm)。recordDate/checkinDate 为纯日期, // 所有环境都能解析。 describe('recordDateOf', () => { test('prefers recordDate over createdAt (补打:recordDate=昨天, createdAt=今天 → 昨天)', () => { // 补打场景:recordDate 是实际用餐日(昨天),createdAt 是记录创建时间(今天)。 // 若 createdAt 优先会误判"今天已打卡";正确应取业务日期 recordDate。 expect(format.recordDateOf({ recordDate: '2026-10-29', createdAt: '2026-10-30T13:13:32.000+08:00' })).toBe('2026-10-29') }) test('falls back to createdAt when business date field missing', () => { expect(format.recordDateOf({ createdAt: '2026-10-30T13:13:32.000+08:00' })).toBe('2026-10-30') }) test('prefers startTime/sleepTime/checkinDate (运动/睡眠/心情业务字段优先)', () => { expect(format.recordDateOf({ startTime: '2026-10-30T06:00:00.000+08:00', createdAt: '2026-10-31T00:00:00.000+08:00' })).toBe('2026-10-30') expect(format.recordDateOf({ sleepTime: '2026-10-30T22:00:00.000+08:00', createdAt: '2026-10-31T00:00:00.000+08:00' })).toBe('2026-10-30') expect(format.recordDateOf({ checkinDate: '2026-10-30', createdAt: '2026-10-31T00:00:00.000+08:00' })).toBe('2026-10-30') }) test('returns empty string for null/empty record', () => { expect(format.recordDateOf(null)).toBe('') expect(format.recordDateOf({})).toBe('') }) test('parses ISO 8601 with milliseconds + timezone offset (后端 Jackson 默认输出)', () => { // 后端 Date 序列化真实格式:2026-10-30T13:13:32.000+08:00。 // recordDateOf 对纯 createdAt 也应能正确解析出日期。 expect(format.recordDateOf({ createdAt: '2026-10-30T13:13:32.000+08:00' })).toBe('2026-10-30') }) }) })