Inject Clock into CalendarViewModel for testability, add kotlinx-coroutines-test dependency, replace placeholder test with real coverage for ISO week numbers, month day grids, page mapping, and utility functions. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
255 lines
7.6 KiB
Kotlin
255 lines
7.6 KiB
Kotlin
package plus.rua.project.ui
|
|
|
|
import kotlinx.datetime.DatePeriod
|
|
import kotlinx.datetime.LocalDate
|
|
import kotlinx.datetime.minus
|
|
import kotlinx.datetime.plus
|
|
import kotlin.test.Test
|
|
import kotlin.test.assertEquals
|
|
|
|
class CalendarUtilsTest {
|
|
|
|
// ---- calculateWeeksCount ----
|
|
|
|
@Test
|
|
fun calculateWeeksCount_normalFebruary_returns4Rows() {
|
|
// Feb 2027: starts on Monday, 28 days -> exactly 4 rows
|
|
assertEquals(4, calculateWeeksCount(2027, 2))
|
|
}
|
|
|
|
@Test
|
|
fun calculateWeeksCount_leapYearFebruary_returns5Rows() {
|
|
// Feb 2024: starts on Thursday, 29 days -> 5 rows
|
|
assertEquals(5, calculateWeeksCount(2024, 2))
|
|
}
|
|
|
|
@Test
|
|
fun calculateWeeksCount_sixRowMonth_returns6Rows() {
|
|
// Mar 2026: starts on Sunday (ordinal=6), 31 days -> 6 rows
|
|
assertEquals(6, calculateWeeksCount(2026, 3))
|
|
}
|
|
|
|
@Test
|
|
fun calculateWeeksCount_monthStartingMonday_31days_returns5Rows() {
|
|
// Jan 2027: starts on Friday (ordinal=4), 31 days
|
|
// offset=4, days=31, (4+31-1)/7 + 1 = 34/7 + 1 = 4+1 = 5
|
|
assertEquals(5, calculateWeeksCount(2027, 1))
|
|
}
|
|
|
|
@Test
|
|
fun calculateWeeksCount_monthStartingSunday_returns6Rows() {
|
|
// Jun 2025: starts on Sunday (ordinal=6), 30 days
|
|
// offset=6, days=30, (6+30-1)/7 + 1 = 35/7 + 1 = 5+1 = 6
|
|
assertEquals(6, calculateWeeksCount(2025, 6))
|
|
}
|
|
|
|
@Test
|
|
fun calculateWeeksCount_30dayMonthStartingSaturday_returns5Rows() {
|
|
// Nov 2025: starts on Saturday (ordinal=5), 30 days
|
|
// offset=5, days=30, (5+30-1)/7 + 1 = 34/7 + 1 = 4+1 = 5
|
|
assertEquals(5, calculateWeeksCount(2025, 11))
|
|
}
|
|
|
|
@Test
|
|
fun calculateWeeksCount_december() {
|
|
// Dec 2026: starts on Wednesday (ordinal=2), 31 days
|
|
// offset=2, days=31, (2+31-1)/7 + 1 = 32/7 + 1 = 4+1 = 5
|
|
assertEquals(5, calculateWeeksCount(2026, 12))
|
|
}
|
|
|
|
// ---- pageToYearMonth / yearMonthToPage ----
|
|
|
|
@Test
|
|
fun pageToYearMonth_centerPage_returnsInitialYearMonth() {
|
|
val (year, month) = pageToYearMonth(START_PAGE, 2026, 5)
|
|
assertEquals(2026, year)
|
|
assertEquals(5, month)
|
|
}
|
|
|
|
@Test
|
|
fun pageToYearMonth_forwardOnePage_returnsNextMonth() {
|
|
val (year, month) = pageToYearMonth(START_PAGE + 1, 2026, 5)
|
|
assertEquals(2026, year)
|
|
assertEquals(6, month)
|
|
}
|
|
|
|
@Test
|
|
fun pageToYearMonth_backwardOnePage_returnsPreviousMonth() {
|
|
val (year, month) = pageToYearMonth(START_PAGE - 1, 2026, 5)
|
|
assertEquals(2026, year)
|
|
assertEquals(4, month)
|
|
}
|
|
|
|
@Test
|
|
fun pageToYearMonth_crossYearBoundary_forward() {
|
|
// From Dec 2026, forward 1 page -> Jan 2027
|
|
val (year, month) = pageToYearMonth(START_PAGE + 1, 2026, 12)
|
|
assertEquals(2027, year)
|
|
assertEquals(1, month)
|
|
}
|
|
|
|
@Test
|
|
fun pageToYearMonth_crossYearBoundary_backward() {
|
|
// From Jan 2026, backward 1 page -> Dec 2025
|
|
val (year, month) = pageToYearMonth(START_PAGE - 1, 2026, 1)
|
|
assertEquals(2025, year)
|
|
assertEquals(12, month)
|
|
}
|
|
|
|
@Test
|
|
fun pageToYearMonth_manyPagesForward() {
|
|
// 12 pages forward from May 2026 -> May 2027
|
|
val (year, month) = pageToYearMonth(START_PAGE + 12, 2026, 5)
|
|
assertEquals(2027, year)
|
|
assertEquals(5, month)
|
|
}
|
|
|
|
@Test
|
|
fun yearMonthToPage_centerMonth_returnsStartPage() {
|
|
assertEquals(START_PAGE, yearMonthToPage(2026, 5, 2026, 5))
|
|
}
|
|
|
|
@Test
|
|
fun yearMonthToPage_nextMonth_returnsNextPage() {
|
|
assertEquals(START_PAGE + 1, yearMonthToPage(2026, 6, 2026, 5))
|
|
}
|
|
|
|
@Test
|
|
fun yearMonthToPage_previousMonth_returnsPreviousPage() {
|
|
assertEquals(START_PAGE - 1, yearMonthToPage(2026, 4, 2026, 5))
|
|
}
|
|
|
|
@Test
|
|
fun yearMonthToPage_crossYearBoundary() {
|
|
assertEquals(START_PAGE + 1, yearMonthToPage(2027, 1, 2026, 12))
|
|
}
|
|
|
|
@Test
|
|
fun pageToYearMonth_yearMonthRoundTrip() {
|
|
// Converting page -> yearMonth -> page should return the original page
|
|
val initialYear = 2026
|
|
val initialMonth = 5
|
|
for (offset in -24..24) {
|
|
val page = START_PAGE + offset
|
|
val (y, m) = pageToYearMonth(page, initialYear, initialMonth)
|
|
val roundTrip = yearMonthToPage(y, m, initialYear, initialMonth)
|
|
assertEquals(page, roundTrip, "Round-trip failed for offset=$offset")
|
|
}
|
|
}
|
|
|
|
// ---- LocalDate.toWeekMonday ----
|
|
|
|
@Test
|
|
fun toWeekMonday_monday_returnsItself() {
|
|
val monday = LocalDate(2026, 5, 11) // Monday
|
|
assertEquals(monday, monday.toWeekMonday())
|
|
}
|
|
|
|
@Test
|
|
fun toWeekMonday_tuesday_returnsPreviousMonday() {
|
|
val tuesday = LocalDate(2026, 5, 12)
|
|
assertEquals(LocalDate(2026, 5, 11), tuesday.toWeekMonday())
|
|
}
|
|
|
|
@Test
|
|
fun toWeekMonday_sunday_returnsPreviousMonday() {
|
|
val sunday = LocalDate(2026, 5, 17) // Sunday
|
|
assertEquals(LocalDate(2026, 5, 11), sunday.toWeekMonday())
|
|
}
|
|
|
|
@Test
|
|
fun toWeekMonday_crossMonthBoundary() {
|
|
// June 1, 2026 is a Monday - so May 31 (Sunday) should return May 25
|
|
val sunday = LocalDate(2026, 5, 31)
|
|
assertEquals(LocalDate(2026, 5, 25), sunday.toWeekMonday())
|
|
}
|
|
|
|
@Test
|
|
fun toWeekMonday_crossYearBoundary() {
|
|
// Jan 1, 2026 is a Thursday. Monday of that week is Dec 29, 2025
|
|
val thursday = LocalDate(2026, 1, 1)
|
|
assertEquals(LocalDate(2025, 12, 29), thursday.toWeekMonday())
|
|
}
|
|
|
|
@Test
|
|
fun toWeekMonday_saturday_returnsPreviousMonday() {
|
|
val saturday = LocalDate(2026, 5, 16)
|
|
assertEquals(LocalDate(2026, 5, 11), saturday.toWeekMonday())
|
|
}
|
|
|
|
@Test
|
|
fun toWeekMonday_wednesday_returnsPreviousMonday() {
|
|
val wednesday = LocalDate(2026, 5, 13)
|
|
assertEquals(LocalDate(2026, 5, 11), wednesday.toWeekMonday())
|
|
}
|
|
|
|
// ---- pageToWeekMonday ----
|
|
|
|
@Test
|
|
fun pageToWeekMonday_centerPage_returnsInitial() {
|
|
val initial = LocalDate(2026, 5, 11) // Monday
|
|
assertEquals(initial, pageToWeekMonday(START_PAGE, initial))
|
|
}
|
|
|
|
@Test
|
|
fun pageToWeekMonday_forwardOnePage_returnsNextWeekMonday() {
|
|
val initial = LocalDate(2026, 5, 11)
|
|
assertEquals(LocalDate(2026, 5, 18), pageToWeekMonday(START_PAGE + 1, initial))
|
|
}
|
|
|
|
@Test
|
|
fun pageToWeekMonday_backwardOnePage_returnsPreviousWeekMonday() {
|
|
val initial = LocalDate(2026, 5, 11)
|
|
assertEquals(LocalDate(2026, 5, 4), pageToWeekMonday(START_PAGE - 1, initial))
|
|
}
|
|
|
|
@Test
|
|
fun pageToWeekMonday_forwardMultiplePages() {
|
|
val initial = LocalDate(2026, 5, 11)
|
|
assertEquals(LocalDate(2026, 6, 8), pageToWeekMonday(START_PAGE + 4, initial))
|
|
}
|
|
|
|
@Test
|
|
fun pageToWeekMonday_backwardMultiplePages_crossMonth() {
|
|
val initial = LocalDate(2026, 5, 11)
|
|
assertEquals(LocalDate(2026, 4, 13), pageToWeekMonday(START_PAGE - 4, initial))
|
|
}
|
|
|
|
// ---- lerp ----
|
|
|
|
@Test
|
|
fun lerp_fractionZero_returnsStart() {
|
|
assertEquals(0f, lerp(0f, 100f, 0f), 0.01f)
|
|
}
|
|
|
|
@Test
|
|
fun lerp_fractionOne_returnsEnd() {
|
|
assertEquals(100f, lerp(0f, 100f, 1f), 0.01f)
|
|
}
|
|
|
|
@Test
|
|
fun lerp_fractionHalf_returnsMidpoint() {
|
|
assertEquals(50f, lerp(0f, 100f, 0.5f), 0.01f)
|
|
}
|
|
|
|
@Test
|
|
fun lerp_negativeRange() {
|
|
assertEquals(0f, lerp(-50f, 50f, 0.5f), 0.01f)
|
|
}
|
|
|
|
@Test
|
|
fun lerp_sameStartAndEnd_returnsSame() {
|
|
assertEquals(42f, lerp(42f, 42f, 0.5f), 0.01f)
|
|
}
|
|
|
|
@Test
|
|
fun lerp_fractionGreaterThanOne() {
|
|
assertEquals(150f, lerp(0f, 100f, 1.5f), 0.01f)
|
|
}
|
|
|
|
@Test
|
|
fun lerp_negativeFraction() {
|
|
assertEquals(-50f, lerp(0f, 100f, -0.5f), 0.01f)
|
|
}
|
|
}
|