test: 新增 ShiftPattern、CalendarUtils、CalendarViewModel 单元测试

- ShiftPatternTest: 锚点前后、跨周期循环、负天数取模、空 cycle、
  单元素 cycle、多样化周期、data class 属性
- CalendarUtilsExtraTest: calculateWeeksCountForPage(跨月/跨年)、
  relativeDayDescription(今天/昨天/明天/N天前后/跨年月)、
  formatLunarDate(农历前缀/正月初一/多日期验证)
- CalendarViewModelStateTest: 初始状态、selectDate(含 currentMonth/Year 联动)、
  increment/decrementYear、selectMonthFromYearView、shiftKindAt、
  showLegalHoliday、onDrag/onExpandDrag progress 更新与 clamp、
  getMonthDays 与 selectedDate/today 交互

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
xfy 2026-05-19 11:47:08 +08:00
parent 0d58be45bc
commit d3befaabec
3 changed files with 699 additions and 0 deletions

View File

@ -0,0 +1,365 @@
package plus.rua.project
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.datetime.Clock
import kotlinx.datetime.Instant
import kotlinx.datetime.LocalDate
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertNotNull
import kotlin.test.assertNull
import kotlin.test.assertTrue
private class StateTestFixedClock(private val instant: Instant) : Clock {
override fun now(): Instant = instant
}
/**
* 覆盖 [CalendarViewModel] 中与日期选择年视图班次拖拽 progress
* 同步可观察状态相关的逻辑
*
* 动画完成的最终状态例如 [CalendarViewModel.isCollapsed] spring
* 动画结束后的取值需要 MonotonicFrameClock 驱动不在本测试集合范围内
*/
class CalendarViewModelStateTest {
// 固定 today = 2026/5/15
private val fixedInstant = Instant.parse("2026-05-15T00:00:00Z")
private val testClock = StateTestFixedClock(fixedInstant)
private fun createViewModel(): CalendarViewModel {
val scope = CoroutineScope(Dispatchers.Unconfined)
return CalendarViewModel(coroutineScope = scope, clock = testClock)
}
// ---- 初始状态 ----
@Test
fun init_selectedDateIsToday() {
val vm = createViewModel()
assertEquals(LocalDate(2026, 5, 15), vm.selectedDate)
}
@Test
fun init_isCollapsedDefaultsFalse() {
assertFalse(createViewModel().isCollapsed)
}
@Test
fun init_collapseProgressDefaultsZero() {
assertEquals(0f, createViewModel().collapseProgress, 0.001f)
}
@Test
fun init_isYearViewDefaultsFalse() {
assertFalse(createViewModel().isYearView)
}
@Test
fun init_yearViewProgressDefaultsZero() {
assertEquals(0f, createViewModel().yearViewProgress, 0.001f)
}
@Test
fun init_yearViewYearDefaultsToTodayYear() {
assertEquals(2026, createViewModel().yearViewYear)
}
@Test
fun init_showLegalHolidayDefaultsFalse() {
assertFalse(createViewModel().showLegalHoliday)
}
@Test
fun init_shiftPatternHasDefault() {
val pattern = createViewModel().shiftPattern
assertNotNull(pattern)
assertEquals(LocalDate(2026, 5, 15), pattern.anchorDate)
assertEquals(4, pattern.cycle.size)
}
@Test
fun init_currentMonthMatchesToday() {
assertEquals(5, createViewModel().currentMonth)
}
@Test
fun init_currentYearMatchesToday() {
assertEquals(2026, createViewModel().currentYear)
}
// ---- selectDate ----
@Test
fun selectDate_updatesSelectedDate() {
val vm = createViewModel()
vm.selectDate(LocalDate(2026, 6, 1))
assertEquals(LocalDate(2026, 6, 1), vm.selectedDate)
}
@Test
fun selectDate_currentMonthFollowsSelection() {
val vm = createViewModel()
vm.selectDate(LocalDate(2026, 8, 20))
assertEquals(8, vm.currentMonth)
assertEquals(2026, vm.currentYear)
}
@Test
fun selectDate_yearFollowsSelection() {
val vm = createViewModel()
vm.selectDate(LocalDate(2027, 1, 1))
assertEquals(2027, vm.currentYear)
assertEquals(1, vm.currentMonth)
}
@Test
fun selectDate_pastDate_updatesCorrectly() {
val vm = createViewModel()
vm.selectDate(LocalDate(2020, 12, 31))
assertEquals(LocalDate(2020, 12, 31), vm.selectedDate)
assertEquals(12, vm.currentMonth)
assertEquals(2020, vm.currentYear)
}
// ---- incrementYear / decrementYear ----
@Test
fun incrementYear_increasesYearViewYear() {
val vm = createViewModel()
vm.incrementYear()
assertEquals(2027, vm.yearViewYear)
}
@Test
fun decrementYear_decreasesYearViewYear() {
val vm = createViewModel()
vm.decrementYear()
assertEquals(2025, vm.yearViewYear)
}
@Test
fun incrementDecrementYear_consecutiveCalls() {
val vm = createViewModel()
repeat(5) { vm.incrementYear() }
assertEquals(2031, vm.yearViewYear)
repeat(3) { vm.decrementYear() }
assertEquals(2028, vm.yearViewYear)
}
@Test
fun incrementYear_doesNotAffectSelectedDate() {
val vm = createViewModel()
val before = vm.selectedDate
vm.incrementYear()
assertEquals(before, vm.selectedDate)
}
// ---- selectMonthFromYearView ----
@Test
fun selectMonthFromYearView_sameYearOtherMonth_setsFirstDayOfMonth() {
val vm = createViewModel()
vm.selectMonthFromYearView(8)
assertEquals(LocalDate(2026, 8, 1), vm.selectedDate)
}
@Test
fun selectMonthFromYearView_currentYearAndMonth_setsToToday() {
val vm = createViewModel()
// yearViewYear = 2026, today.month = 5
vm.selectMonthFromYearView(5)
assertEquals(LocalDate(2026, 5, 15), vm.selectedDate)
}
@Test
fun selectMonthFromYearView_otherYear_setsFirstDay() {
val vm = createViewModel()
vm.incrementYear() // yearViewYear = 2027
vm.selectMonthFromYearView(5)
assertEquals(LocalDate(2027, 5, 1), vm.selectedDate)
}
@Test
fun selectMonthFromYearView_setsIsYearViewFalse() {
val vm = createViewModel()
vm.selectMonthFromYearView(3)
assertFalse(vm.isYearView)
}
@Test
fun selectMonthFromYearView_january() {
val vm = createViewModel()
vm.selectMonthFromYearView(1)
assertEquals(LocalDate(2026, 1, 1), vm.selectedDate)
}
@Test
fun selectMonthFromYearView_december() {
val vm = createViewModel()
vm.selectMonthFromYearView(12)
assertEquals(LocalDate(2026, 12, 1), vm.selectedDate)
}
// ---- shiftKindAt ----
@Test
fun shiftKindAt_anchorDate_returnsWork() {
// default pattern: anchor 2026-05-15, cycle WORK/WORK/OFF/OFF
val vm = createViewModel()
assertEquals(ShiftKind.WORK, vm.shiftKindAt(LocalDate(2026, 5, 15)))
}
@Test
fun shiftKindAt_dayAfterAnchor_returnsWork() {
val vm = createViewModel()
assertEquals(ShiftKind.WORK, vm.shiftKindAt(LocalDate(2026, 5, 16)))
}
@Test
fun shiftKindAt_twoDaysAfterAnchor_returnsOff() {
val vm = createViewModel()
assertEquals(ShiftKind.OFF, vm.shiftKindAt(LocalDate(2026, 5, 17)))
}
@Test
fun shiftKindAt_nullPattern_returnsNull() {
val vm = createViewModel()
vm.shiftPattern = null
assertNull(vm.shiftKindAt(LocalDate(2026, 5, 15)))
}
@Test
fun shiftKindAt_customPattern_usesNewPattern() {
val vm = createViewModel()
vm.shiftPattern = ShiftPattern(
anchorDate = LocalDate(2026, 5, 15),
cycle = listOf(ShiftKind.OFF, ShiftKind.WORK)
)
assertEquals(ShiftKind.OFF, vm.shiftKindAt(LocalDate(2026, 5, 15)))
assertEquals(ShiftKind.WORK, vm.shiftKindAt(LocalDate(2026, 5, 16)))
assertEquals(ShiftKind.OFF, vm.shiftKindAt(LocalDate(2026, 5, 17)))
}
// ---- showLegalHoliday ----
@Test
fun showLegalHoliday_canBeToggled() {
val vm = createViewModel()
assertFalse(vm.showLegalHoliday)
vm.showLegalHoliday = true
assertTrue(vm.showLegalHoliday)
vm.showLegalHoliday = false
assertFalse(vm.showLegalHoliday)
}
// ---- onDrag: 折叠拖拽同步路径snapTo----
@Test
fun onDrag_positiveDelta_increasesProgress() {
val vm = createViewModel()
vm.onDrag(0.3f)
assertEquals(0.3f, vm.collapseProgress, 0.001f)
}
@Test
fun onDrag_accumulatesAcrossCalls() {
val vm = createViewModel()
vm.onDrag(0.2f)
vm.onDrag(0.3f)
assertEquals(0.5f, vm.collapseProgress, 0.001f)
}
@Test
fun onDrag_clampsAtOne() {
val vm = createViewModel()
vm.onDrag(0.8f)
vm.onDrag(0.8f)
assertEquals(1f, vm.collapseProgress, 0.001f)
}
@Test
fun onDrag_clampsAtZeroWhenNegativeFromZero() {
val vm = createViewModel()
vm.onDrag(-0.3f)
assertEquals(0f, vm.collapseProgress, 0.001f)
}
@Test
fun onDrag_negativeAfterPositive_canDecrease() {
val vm = createViewModel()
vm.onDrag(0.5f)
vm.onDrag(-0.2f)
assertEquals(0.3f, vm.collapseProgress, 0.001f)
}
// ---- onExpandDrag: 展开拖拽 ----
@Test
fun onExpandDrag_updatesProgress() {
val vm = createViewModel()
// 先把 progress 推到 1
vm.onDrag(1f)
assertEquals(1f, vm.collapseProgress, 0.001f)
// 展开方向delta 为负
vm.onExpandDrag(-0.4f)
assertEquals(0.6f, vm.collapseProgress, 0.001f)
}
@Test
fun onExpandDrag_clampsAtZero() {
val vm = createViewModel()
vm.onDrag(0.5f)
vm.onExpandDrag(-1f)
assertEquals(0f, vm.collapseProgress, 0.001f)
}
@Test
fun onExpandDrag_clampsAtOne() {
val vm = createViewModel()
vm.onExpandDrag(2f)
assertEquals(1f, vm.collapseProgress, 0.001f)
}
// ---- getMonthDays 与 selectedDate 配合 ----
@Test
fun getMonthDays_updatesIsSelectedAfterSelectDate() {
val vm = createViewModel()
vm.selectDate(LocalDate(2026, 5, 20))
val days = vm.getMonthDays(2026, 5)
val selectedCell = days.first { it.isSelected }
assertEquals(20, selectedCell.date.day)
}
@Test
fun getMonthDays_noCellSelectedInOtherMonth() {
val vm = createViewModel()
// selectedDate 默认是今天(5/15),不在 2026/8 月内(含跨月填充也不可能)
val days = vm.getMonthDays(2026, 8)
assertTrue(days.none { it.isSelected })
}
@Test
fun getMonthDays_todayCellAlwaysReflectsTodayClock() {
val vm = createViewModel()
// 即便选中其他日期isToday 依然根据 clock 注入的 today
vm.selectDate(LocalDate(2026, 5, 20))
val days = vm.getMonthDays(2026, 5)
val todayCell = days.first { it.isToday }
assertEquals(15, todayCell.date.day)
}
@Test
fun getMonthDays_returnsMultipleOfSeven() {
val vm = createViewModel()
// 任何月份cells 数都应该是 7 的倍数
for (month in 1..12) {
val size = vm.getMonthDays(2026, month).size
assertEquals(0, size % 7, "Month 2026/$month size=$size not multiple of 7")
assertTrue(size in 28..42, "Month 2026/$month size=$size out of [28, 42]")
}
}
}

View File

@ -0,0 +1,179 @@
package plus.rua.project
import kotlinx.datetime.LocalDate
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNull
class ShiftPatternTest {
private val anchor = LocalDate(2026, 5, 15)
private val twoOnTwoOff = ShiftPattern(
anchorDate = anchor,
cycle = listOf(ShiftKind.WORK, ShiftKind.WORK, ShiftKind.OFF, ShiftKind.OFF)
)
// ---- kindAt: 锚点与同周期内 ----
@Test
fun kindAt_anchorDate_returnsFirstInCycle() {
assertEquals(ShiftKind.WORK, twoOnTwoOff.kindAt(anchor))
}
@Test
fun kindAt_oneAfterAnchor_returnsSecondInCycle() {
assertEquals(ShiftKind.WORK, twoOnTwoOff.kindAt(LocalDate(2026, 5, 16)))
}
@Test
fun kindAt_twoAfterAnchor_returnsThirdInCycle() {
assertEquals(ShiftKind.OFF, twoOnTwoOff.kindAt(LocalDate(2026, 5, 17)))
}
@Test
fun kindAt_threeAfterAnchor_returnsFourthInCycle() {
assertEquals(ShiftKind.OFF, twoOnTwoOff.kindAt(LocalDate(2026, 5, 18)))
}
// ---- kindAt: 周期循环 ----
@Test
fun kindAt_fourAfterAnchor_wrapsToCycleStart() {
// (5/19 - 5/15) % 4 = 0
assertEquals(ShiftKind.WORK, twoOnTwoOff.kindAt(LocalDate(2026, 5, 19)))
}
@Test
fun kindAt_eightAfterAnchor_wrapsTwice() {
// (5/23 - 5/15) % 4 = 0
assertEquals(ShiftKind.WORK, twoOnTwoOff.kindAt(LocalDate(2026, 5, 23)))
}
@Test
fun kindAt_oneCycleLater_idx2_returnsOff() {
// (5/21 - 5/15) % 4 = 2
assertEquals(ShiftKind.OFF, twoOnTwoOff.kindAt(LocalDate(2026, 5, 21)))
}
@Test
fun kindAt_manyCyclesLater_correctlyWraps() {
// 100天后: (100) % 4 = 0
assertEquals(ShiftKind.WORK, twoOnTwoOff.kindAt(LocalDate(2026, 8, 23)))
}
// ---- kindAt: 锚点之前的日期(负差值处理)----
@Test
fun kindAt_oneDayBeforeAnchor_returnsLastInCycle() {
// -1 mod 4 = 3 -> OFF (cycle[3])
assertEquals(ShiftKind.OFF, twoOnTwoOff.kindAt(LocalDate(2026, 5, 14)))
}
@Test
fun kindAt_twoDaysBeforeAnchor_returnsThirdInCycle() {
// -2 mod 4 = 2 -> OFF (cycle[2])
assertEquals(ShiftKind.OFF, twoOnTwoOff.kindAt(LocalDate(2026, 5, 13)))
}
@Test
fun kindAt_threeDaysBeforeAnchor_returnsSecondInCycle() {
// -3 mod 4 = 1 -> WORK (cycle[1])
assertEquals(ShiftKind.WORK, twoOnTwoOff.kindAt(LocalDate(2026, 5, 12)))
}
@Test
fun kindAt_fourDaysBeforeAnchor_returnsFirstInCycle() {
// -4 mod 4 = 0 -> WORK (cycle[0])
assertEquals(ShiftKind.WORK, twoOnTwoOff.kindAt(LocalDate(2026, 5, 11)))
}
@Test
fun kindAt_manyDaysBeforeAnchor_correctlyWraps() {
// -100 mod 4 = 0 -> WORK
assertEquals(ShiftKind.WORK, twoOnTwoOff.kindAt(LocalDate(2026, 2, 4)))
}
// ---- kindAt: 边界情况 ----
@Test
fun kindAt_emptyCycle_returnsNull() {
val pattern = ShiftPattern(anchorDate = anchor, cycle = emptyList())
assertNull(pattern.kindAt(anchor))
assertNull(pattern.kindAt(LocalDate(2026, 5, 16)))
assertNull(pattern.kindAt(LocalDate(2026, 5, 14)))
}
@Test
fun kindAt_singleElementCycle_alwaysReturnsThatElement() {
val pattern = ShiftPattern(anchorDate = anchor, cycle = listOf(ShiftKind.WORK))
assertEquals(ShiftKind.WORK, pattern.kindAt(anchor))
assertEquals(ShiftKind.WORK, pattern.kindAt(LocalDate(2026, 5, 20)))
assertEquals(ShiftKind.WORK, pattern.kindAt(LocalDate(2026, 1, 1)))
assertEquals(ShiftKind.WORK, pattern.kindAt(LocalDate(2027, 12, 31)))
}
@Test
fun kindAt_singleOffCycle_alwaysReturnsOff() {
val pattern = ShiftPattern(anchorDate = anchor, cycle = listOf(ShiftKind.OFF))
assertEquals(ShiftKind.OFF, pattern.kindAt(anchor))
assertEquals(ShiftKind.OFF, pattern.kindAt(LocalDate(2030, 6, 15)))
}
// ---- kindAt: 多样化周期 ----
@Test
fun kindAt_threeOnOneOffCycle() {
// 4 day cycle: WORK WORK WORK OFF
val pattern = ShiftPattern(
anchorDate = anchor,
cycle = listOf(ShiftKind.WORK, ShiftKind.WORK, ShiftKind.WORK, ShiftKind.OFF)
)
assertEquals(ShiftKind.WORK, pattern.kindAt(LocalDate(2026, 5, 15))) // 0
assertEquals(ShiftKind.WORK, pattern.kindAt(LocalDate(2026, 5, 16))) // 1
assertEquals(ShiftKind.WORK, pattern.kindAt(LocalDate(2026, 5, 17))) // 2
assertEquals(ShiftKind.OFF, pattern.kindAt(LocalDate(2026, 5, 18))) // 3
assertEquals(ShiftKind.WORK, pattern.kindAt(LocalDate(2026, 5, 19))) // 0
}
@Test
fun kindAt_weekCycle_returnsCorrectDay() {
// 7天周期4天上班3天休息
val pattern = ShiftPattern(
anchorDate = anchor,
cycle = listOf(
ShiftKind.WORK, ShiftKind.WORK, ShiftKind.WORK, ShiftKind.WORK,
ShiftKind.OFF, ShiftKind.OFF, ShiftKind.OFF
)
)
assertEquals(ShiftKind.WORK, pattern.kindAt(LocalDate(2026, 5, 18))) // idx 3
assertEquals(ShiftKind.OFF, pattern.kindAt(LocalDate(2026, 5, 19))) // idx 4
assertEquals(ShiftKind.OFF, pattern.kindAt(LocalDate(2026, 5, 21))) // idx 6
assertEquals(ShiftKind.WORK, pattern.kindAt(LocalDate(2026, 5, 22))) // idx 0 (next cycle)
}
// ---- ShiftPattern: 元数据 ----
@Test
fun shiftPattern_defaultNameIsChinese() {
val pattern = ShiftPattern(anchorDate = anchor, cycle = listOf(ShiftKind.WORK))
assertEquals("默认", pattern.name)
}
@Test
fun shiftPattern_customNameIsPreserved() {
val pattern = ShiftPattern(
anchorDate = anchor,
cycle = listOf(ShiftKind.WORK),
name = "夜班"
)
assertEquals("夜班", pattern.name)
}
@Test
fun shiftPattern_dataClassEquality() {
val a = ShiftPattern(anchor, listOf(ShiftKind.WORK, ShiftKind.OFF))
val b = ShiftPattern(anchor, listOf(ShiftKind.WORK, ShiftKind.OFF))
assertEquals(a, b)
assertEquals(a.hashCode(), b.hashCode())
}
}

View File

@ -0,0 +1,155 @@
package plus.rua.project.ui
import kotlinx.datetime.LocalDate
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
/**
* 测试 CalendarUtils 中尚未被 [CalendarUtilsTest] 覆盖的函数
* - [calculateWeeksCountForPage]
* - [relativeDayDescription]
* - [formatLunarDate]
*/
class CalendarUtilsExtraTest {
// ---- calculateWeeksCountForPage ----
@Test
fun calculateWeeksCountForPage_centerPage_returnsCurrentMonthRows() {
// today = 2026/5/15 (May), May 2026 has 5 rows
val today = LocalDate(2026, 5, 15)
assertEquals(calculateWeeksCount(2026, 5), calculateWeeksCountForPage(START_PAGE, today))
}
@Test
fun calculateWeeksCountForPage_forwardOnePage_returnsNextMonthRows() {
// From May 2026, +1 -> June 2026
val today = LocalDate(2026, 5, 15)
assertEquals(calculateWeeksCount(2026, 6), calculateWeeksCountForPage(START_PAGE + 1, today))
}
@Test
fun calculateWeeksCountForPage_backwardOnePage_returnsPreviousMonthRows() {
// From May 2026, -1 -> April 2026
val today = LocalDate(2026, 5, 15)
assertEquals(calculateWeeksCount(2026, 4), calculateWeeksCountForPage(START_PAGE - 1, today))
}
@Test
fun calculateWeeksCountForPage_crossYearForward() {
// From December 2026, +1 -> January 2027
val today = LocalDate(2026, 12, 10)
assertEquals(calculateWeeksCount(2027, 1), calculateWeeksCountForPage(START_PAGE + 1, today))
}
@Test
fun calculateWeeksCountForPage_crossYearBackward() {
// From January 2026, -1 -> December 2025
val today = LocalDate(2026, 1, 10)
assertEquals(calculateWeeksCount(2025, 12), calculateWeeksCountForPage(START_PAGE - 1, today))
}
@Test
fun calculateWeeksCountForPage_twelvePagesForward_returnsSameMonthOfNextYear() {
val today = LocalDate(2026, 5, 15)
// +12 -> May 2027
assertEquals(calculateWeeksCount(2027, 5), calculateWeeksCountForPage(START_PAGE + 12, today))
}
// ---- relativeDayDescription ----
@Test
fun relativeDayDescription_today_returnsToday() {
val today = LocalDate(2026, 5, 19)
assertEquals("今天", relativeDayDescription(today, today))
}
@Test
fun relativeDayDescription_yesterday_returnsYesterday() {
val today = LocalDate(2026, 5, 19)
val yesterday = LocalDate(2026, 5, 18)
assertEquals("昨天", relativeDayDescription(yesterday, today))
}
@Test
fun relativeDayDescription_tomorrow_returnsTomorrow() {
val today = LocalDate(2026, 5, 19)
val tomorrow = LocalDate(2026, 5, 20)
assertEquals("明天", relativeDayDescription(tomorrow, today))
}
@Test
fun relativeDayDescription_twoDaysBefore_returnsXDaysAgo() {
val today = LocalDate(2026, 5, 19)
assertEquals("2天前", relativeDayDescription(LocalDate(2026, 5, 17), today))
}
@Test
fun relativeDayDescription_twoDaysAfter_returnsXDaysLater() {
val today = LocalDate(2026, 5, 19)
assertEquals("2天后", relativeDayDescription(LocalDate(2026, 5, 21), today))
}
@Test
fun relativeDayDescription_aWeekBefore_returnsCorrectDays() {
val today = LocalDate(2026, 5, 19)
assertEquals("7天前", relativeDayDescription(LocalDate(2026, 5, 12), today))
}
@Test
fun relativeDayDescription_thirtyDaysAfter_returnsCorrectDays() {
val today = LocalDate(2026, 5, 1)
assertEquals("30天后", relativeDayDescription(LocalDate(2026, 5, 31), today))
}
@Test
fun relativeDayDescription_crossMonthBackward_returnsCorrectDays() {
val today = LocalDate(2026, 5, 2)
assertEquals("3天前", relativeDayDescription(LocalDate(2026, 4, 29), today))
}
@Test
fun relativeDayDescription_crossYearForward_returnsCorrectDays() {
val today = LocalDate(2025, 12, 30)
assertEquals("5天后", relativeDayDescription(LocalDate(2026, 1, 4), today))
}
// ---- formatLunarDate ----
@Test
fun formatLunarDate_startsWithLunarPrefix() {
val result = formatLunarDate(LocalDate(2026, 5, 19))
assertTrue(result.startsWith("农历"), "Expected to start with '农历', got: $result")
}
@Test
fun formatLunarDate_january1_2026_returnsCorrectLunar() {
// 2026/1/1 公历 -> 2025年农历十一月十二
val result = formatLunarDate(LocalDate(2026, 1, 1))
assertTrue(result.startsWith("农历"), "Expected '农历' prefix, got: $result")
// 验证不是空字符串
assertTrue(result.length > 2, "Lunar date description should contain month and day")
}
@Test
fun formatLunarDate_lunarNewYear2026_returnsFirstDayOfFirstMonth() {
// 2026年农历正月初一 = 2026/2/17 公历
val result = formatLunarDate(LocalDate(2026, 2, 17))
assertEquals("农历正月初一", result)
}
@Test
fun formatLunarDate_anyDate_containsMonthAndDayNames() {
// 仅验证格式:农历 + 月 + 日
for (day in listOf(
LocalDate(2026, 3, 1),
LocalDate(2026, 6, 30),
LocalDate(2026, 12, 25)
)) {
val result = formatLunarDate(day)
assertTrue(result.startsWith("农历"), "Expected '农历' prefix for $day, got: $result")
assertTrue(result.length >= 5, "Result for $day too short: $result")
}
}
}