Skip to content

Commit b380e14

Browse files
committed
🧪 [Hooks]: Enhance table tests
1 parent 4f152c6 commit b380e14

31 files changed

Lines changed: 663 additions & 285 deletions

File tree

hooks/src/commonMain/kotlin/xyz/junerver/compose/hooks/useCountdown.kt

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -49,45 +49,45 @@ private fun useCountdown(options: UseCountdownOptions): CountdownHolder {
4949
require(leftTime.asBoolean() || targetDate.asBoolean()) {
5050
"'leftTime' or 'targetDate' must be set"
5151
}
52-
val leftTimeState = useLatestState(leftTime)
53-
val targetDateState = useLatestState(targetDate)
54-
val target by useState {
55-
if (leftTimeState.value.asBoolean()) {
56-
currentInstant + leftTimeState.value!!
52+
val targetRef = useRef<Instant?>(null)
53+
val (timeLeft, setTimeLeft) = useGetState(Duration.ZERO)
54+
55+
useEffect(leftTime, targetDate) {
56+
targetRef.current = if (leftTime.asBoolean()) {
57+
currentInstant + leftTime
5758
} else {
58-
targetDateState.value
59+
targetDate
5960
}
61+
setTimeLeft(calcLeft(targetRef.current))
6062
}
61-
62-
val (timeLeft, setTimeLeft) = useGetState(calcLeft(target))
6363
val onEndRef by useLatestRef(value = onEnd)
6464
var pauseRef by useRef(default = {})
6565
val (resume, pause) = useInterval(
6666
optionsOf = {
6767
period = interval
6868
},
6969
) {
70-
val targetLeft = calcLeft(target)
70+
val targetLeft = calcLeft(targetRef.current)
7171
setTimeLeft(targetLeft)
7272
if (targetLeft == Duration.ZERO) {
7373
pauseRef()
7474
onEndRef?.invoke()
7575
}
7676
}
77-
useEffect(targetDate) {
77+
useEffect(targetRef.current) {
7878
resume()
7979
}
8080
pauseRef = pause
8181
useEffect(interval) {
82-
if (!target.asBoolean()) {
82+
if (!targetRef.current.asBoolean()) {
8383
setTimeLeft(Duration.ZERO)
8484
return@useEffect
8585
}
86-
setTimeLeft(calcLeft(target))
86+
setTimeLeft(calcLeft(targetRef.current))
8787
resume()
8888
}
89-
val formatRes = useState { parseDuration(timeLeft.value) }
90-
return remember { CountdownHolder(timeLeft, formatRes) }
89+
val formatResState = useState(timeLeft.value) { parseDuration(timeLeft.value) }
90+
return remember { CountdownHolder(timeLeft, formatResState) }
9191
}
9292

9393
/**

hooks/src/commonMain/kotlin/xyz/junerver/compose/hooks/useTimeoutFn.kt

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -81,13 +81,16 @@ private class TimeoutFn(private val options: UseTimeoutFnOptions) {
8181

8282
scope.launch {
8383
isPendingState?.value = true
84-
if (options.immediateCallback) {
85-
timeoutFn.current(this)
86-
} else {
87-
delay(interval)
88-
timeoutFn.current(this)
84+
try {
85+
if (options.immediateCallback) {
86+
timeoutFn.current(this)
87+
} else {
88+
delay(interval)
89+
timeoutFn.current(this)
90+
}
91+
} finally {
92+
isPendingState?.value = false
8993
}
90-
isPendingState?.value = false
9194
}.also { timeoutJob = it }
9295
}
9396

hooks/src/commonMain/kotlin/xyz/junerver/compose/hooks/usetable/Table.kt

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,11 @@ import androidx.compose.runtime.Composable
44
import androidx.compose.runtime.Stable
55
import androidx.compose.runtime.getValue
66
import androidx.compose.runtime.remember
7-
import androidx.compose.ui.Modifier
8-
import kotlin.math.ceil
97
import xyz.junerver.compose.hooks.createContext
10-
import xyz.junerver.compose.hooks.useContext
118
import xyz.junerver.compose.hooks.usetable.core.ColumnDef
129
import xyz.junerver.compose.hooks.usetable.core.Row
1310
import xyz.junerver.compose.hooks.usetable.state.TableState
11+
import xyz.junerver.compose.hooks.usetable.features.pagination.PaginationFeature
1412

1513
/**
1614
* Internal table context for managing table state across components.
@@ -71,7 +69,8 @@ class TableScope<T>(val table: TableHolder<T>) {
7169
) {
7270
val columns by table.columns
7371
val tableState by table.state
74-
content(columns, tableState)
72+
val visibleColumns = columns.filter { tableState.columnVisibility.columnVisibility[it.id] != false }
73+
content(visibleColumns, tableState)
7574
}
7675

7776
/**
@@ -103,11 +102,10 @@ class TableScope<T>(val table: TableHolder<T>) {
103102
val pageIndex = tableState.pagination.pageIndex
104103
val pageSize = tableState.pagination.pageSize
105104
val totalRows = rowModel.totalRows
106-
107-
// Logic calculation for pagination
108-
val pageCount = if (pageSize <= 0) 1 else ceil(totalRows.toDouble() / pageSize).toInt().coerceAtLeast(1)
109-
val canNext = pageIndex < pageCount - 1
110-
val canPrev = pageIndex > 0
105+
106+
val pageCount = PaginationFeature.pageCount(totalRows, pageSize)
107+
val canNext = PaginationFeature.canNext(pageIndex, pageCount)
108+
val canPrev = PaginationFeature.canPrev(pageIndex)
111109

112110
val scope = PaginationScope(
113111
pageIndex = pageIndex,

hooks/src/commonMain/kotlin/xyz/junerver/compose/hooks/usetable/core/TableFeature.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ interface TableFeature<T> {
1717
* Transform the row model.
1818
* Now includes [columns] to allow features to look up column definitions (e.g. for sorting/filtering).
1919
*/
20-
suspend fun transform(
20+
fun transform(
2121
rows: List<Row<T>>,
2222
state: TableState<T>,
2323
columns: List<ColumnDef<T, *>>

hooks/src/commonMain/kotlin/xyz/junerver/compose/hooks/usetable/features/columnSizing/ColumnSizingFeature.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ class ColumnSizingFeature<T> : TableFeature<T> {
1616
// State wiring in Phase 7
1717
}
1818

19-
override suspend fun transform(
19+
override fun transform(
2020
rows: List<Row<T>>,
2121
state: TableState<T>,
2222
columns: List<ColumnDef<T, *>>

hooks/src/commonMain/kotlin/xyz/junerver/compose/hooks/usetable/features/columnVisibility/ColumnVisibilityFeature.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ class ColumnVisibilityFeature<T> : TableFeature<T> {
1616
// State wiring in Phase 7
1717
}
1818

19-
override suspend fun transform(
19+
override fun transform(
2020
rows: List<Row<T>>,
2121
state: TableState<T>,
2222
columns: List<ColumnDef<T, *>>

hooks/src/commonMain/kotlin/xyz/junerver/compose/hooks/usetable/features/expansion/ExpansionFeature.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ class ExpansionFeature<T> : TableFeature<T> {
1616
// State wiring will be done in Phase 7 (useTable hook)
1717
}
1818

19-
override suspend fun transform(
19+
override fun transform(
2020
rows: List<Row<T>>,
2121
state: TableState<T>,
2222
columns: List<ColumnDef<T, *>>

hooks/src/commonMain/kotlin/xyz/junerver/compose/hooks/usetable/features/filtering/FilteringFeature.kt

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ class FilteringFeature<T> : TableFeature<T> {
1616
// TODO: Register state and API
1717
}
1818

19-
override suspend fun transform(
19+
override fun transform(
2020
rows: List<Row<T>>,
2121
state: TableState<T>,
2222
columns: List<ColumnDef<T, *>>
@@ -28,24 +28,24 @@ class FilteringFeature<T> : TableFeature<T> {
2828
return rows
2929
}
3030

31+
val filterableColumns = columns.filter { it.enableFiltering }
32+
if (filterableColumns.isEmpty()) {
33+
return rows
34+
}
35+
3136
return rows.filter { row ->
32-
// 1. Column Filters
3337
val passColumnFilters = columnFilters.all { (colId, filterValue) ->
3438
if (filterValue == null) return@all true
35-
val column = columns.find { it.id == colId } ?: return@all true
36-
39+
val column = filterableColumns.find { it.id == colId } ?: return@all true
40+
3741
val cellValue = row.getValue(column)
38-
// Simple equality check for Phase 1.
39-
// Phase 3 refactor will add FilterFns (includes, equals, etc.)
4042
cellValue.toString().contains(filterValue.toString(), ignoreCase = true)
4143
}
4244

4345
if (!passColumnFilters) return@filter false
4446

45-
// 2. Global Filter
4647
if (globalFilter.isNotBlank()) {
47-
// Search across all columns
48-
return@filter columns.any { column ->
48+
return@filter filterableColumns.any { column ->
4949
val cellValue = row.getValue(column)
5050
cellValue.toString().contains(globalFilter, ignoreCase = true)
5151
}

hooks/src/commonMain/kotlin/xyz/junerver/compose/hooks/usetable/features/grouping/GroupingFeature.kt

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ class GroupingFeature<T> : TableFeature<T> {
1616
// State wiring in Phase 7
1717
}
1818

19-
override suspend fun transform(
19+
override fun transform(
2020
rows: List<Row<T>>,
2121
state: TableState<T>,
2222
columns: List<ColumnDef<T, *>>
@@ -28,15 +28,16 @@ class GroupingFeature<T> : TableFeature<T> {
2828
val groupColumn = columns.find { it.id == grouping.first() } ?: return rows
2929

3030
val groups = rows.groupBy { row -> row.getValue(groupColumn) }
31-
31+
3232
return groups.map { (groupValue, groupRows) ->
33+
val groupKey = groupValue?.toString() ?: "null"
3334
Row(
34-
id = "group-${groupValue.hashCode()}",
35+
id = "group-${groupColumn.id}-$groupKey",
3536
original = groupRows.first().original,
3637
index = 0,
3738
depth = 0,
3839
subRows = groupRows,
39-
metadata = mapOf("isGroupHeader" to true, "groupValue" to groupValue)
40+
metadata = mapOf("isGroupHeader" to true, "groupValue" to groupValue, "groupKey" to groupKey)
4041
)
4142
}
4243
}
Lines changed: 32 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,54 @@
11
package xyz.junerver.compose.hooks.usetable.features.pagination
22

33
import androidx.compose.runtime.Composable
4+
import kotlin.math.ceil
5+
import kotlin.math.min
46
import xyz.junerver.compose.hooks.usetable.core.ColumnDef
57
import xyz.junerver.compose.hooks.usetable.core.Row
68
import xyz.junerver.compose.hooks.usetable.core.TableFeature
79
import xyz.junerver.compose.hooks.usetable.core.TableInstance
10+
import xyz.junerver.compose.hooks.usetable.state.PaginationState
811
import xyz.junerver.compose.hooks.usetable.state.TableState
9-
import kotlin.math.ceil
10-
import kotlin.math.max
11-
import kotlin.math.min
1212

1313
class PaginationFeature<T> : TableFeature<T> {
1414
override val featureId = "pagination"
15-
override val priority = 400 // Execute LAST (after filtering & sorting)
15+
override val priority = 400
1616

1717
@Composable
1818
override fun initState(instance: TableInstance<T>) {
19-
// In actual hook implementation, we will wire up the state setters here.
20-
// For now, we define the transform logic.
2119
}
2220

23-
override suspend fun transform(
21+
override fun transform(
2422
rows: List<Row<T>>,
2523
state: TableState<T>,
2624
columns: List<ColumnDef<T, *>>
2725
): List<Row<T>> {
28-
val pageIndex = state.pagination.pageIndex
29-
val pageSize = state.pagination.pageSize
30-
31-
if (pageSize <= 0) return rows
32-
33-
val startIndex = pageIndex * pageSize
34-
// Boundary check
35-
if (startIndex >= rows.size) return emptyList()
36-
37-
val endIndex = min(startIndex + pageSize, rows.size)
38-
return rows.subList(startIndex, endIndex)
26+
return paginate(rows, state.pagination)
27+
}
28+
29+
companion object {
30+
fun pageCount(totalRows: Int, pageSize: Int): Int {
31+
if (pageSize <= 0) return 1
32+
return ceil(totalRows.toDouble() / pageSize).toInt().coerceAtLeast(1)
33+
}
34+
35+
fun canNext(pageIndex: Int, pageCount: Int): Boolean {
36+
return pageIndex < pageCount - 1
37+
}
38+
39+
fun canPrev(pageIndex: Int): Boolean {
40+
return pageIndex > 0
41+
}
42+
43+
fun <T> paginate(rows: List<Row<T>>, pagination: PaginationState): List<Row<T>> {
44+
val pageSize = pagination.pageSize
45+
if (pageSize <= 0) return rows
46+
47+
val startIndex = (pagination.pageIndex * pageSize).coerceAtLeast(0)
48+
if (startIndex >= rows.size) return emptyList()
49+
50+
val endIndex = min(startIndex + pageSize, rows.size)
51+
return rows.subList(startIndex, endIndex)
52+
}
3953
}
4054
}

0 commit comments

Comments
 (0)