Add guidelines and tests for Komponent development
Some checks failed
Gradle CI / build (push) Has been cancelled
Some checks failed
Gradle CI / build (push) Has been cancelled
Introduce detailed development guidelines for the Kotlin Komponent (Komp) library, covering build instructions, testing, optimization, and code style. Additionally, add a new test case for verifying dynamic row insertion in a table component.
This commit is contained in:
144
.junie/guidelines.md
Normal file
144
.junie/guidelines.md
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
# Kotlin Komponent (Komp) Development Guidelines
|
||||||
|
|
||||||
|
This document provides specific information for developing with the Kotlin Komponent (Komp) library, a component-based UI library for Kotlin/JS.
|
||||||
|
|
||||||
|
## Build/Configuration Instructions
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
- Kotlin 2.1.10 or higher
|
||||||
|
- Gradle 7.0 or higher
|
||||||
|
|
||||||
|
### Building the Project
|
||||||
|
|
||||||
|
The project uses Kotlin Multiplatform with a focus on JavaScript (and potentially WebAssembly in the future).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Build the project
|
||||||
|
./gradlew build
|
||||||
|
|
||||||
|
# Build only JS target
|
||||||
|
./gradlew jsJar
|
||||||
|
|
||||||
|
# Publish to Maven Local for local development
|
||||||
|
./gradlew publishToMavenLocal
|
||||||
|
```
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
|
||||||
|
The project uses the following Gradle plugins:
|
||||||
|
- Kotlin Multiplatform
|
||||||
|
- Maven Publish
|
||||||
|
- Dokka for documentation
|
||||||
|
|
||||||
|
Key configuration files:
|
||||||
|
- `build.gradle.kts` - Main build configuration
|
||||||
|
- `gradle.properties` - Contains publishing credentials and signing configuration
|
||||||
|
- `settings.gradle.kts` - Project settings and repository configuration
|
||||||
|
|
||||||
|
## Testing Information
|
||||||
|
|
||||||
|
### Running Tests
|
||||||
|
|
||||||
|
Tests are written using the Kotlin Test library and run with Karma using Chrome Headless.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run all tests
|
||||||
|
./gradlew jsTest
|
||||||
|
|
||||||
|
# Run browser tests
|
||||||
|
./gradlew jsBrowserTest
|
||||||
|
```
|
||||||
|
|
||||||
|
### Test Structure
|
||||||
|
|
||||||
|
Tests are located in the `src/jsTest` directory. The project uses the standard Kotlin Test library with annotations:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
@Test
|
||||||
|
fun testSomething() {
|
||||||
|
// Test code here
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Writing New Tests
|
||||||
|
|
||||||
|
When writing tests for Komponents:
|
||||||
|
|
||||||
|
1. Create a test class in the `src/jsTest/kotlin/nl/astraeus/komp` directory
|
||||||
|
2. Use the `@Test` annotation for test methods
|
||||||
|
3. For UI component tests:
|
||||||
|
- Create a test DOM element using `document.createElement("div")`
|
||||||
|
- Create your Komponent instance
|
||||||
|
- Render it using `Komponent.create(element, komponent)`
|
||||||
|
- Modify state and call `requestImmediateUpdate()` to test updates
|
||||||
|
- Verify the DOM structure using assertions
|
||||||
|
|
||||||
|
### Example Test
|
||||||
|
|
||||||
|
Here's a simple test example:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
@Test
|
||||||
|
fun testSimpleComponent() {
|
||||||
|
// Create a test component
|
||||||
|
val component = SimpleKomponent()
|
||||||
|
val div = document.createElement("div") as HTMLDivElement
|
||||||
|
|
||||||
|
// Render it
|
||||||
|
Komponent.create(div, component)
|
||||||
|
|
||||||
|
// Verify initial state
|
||||||
|
assertEquals("Hello", div.querySelector("div")?.textContent)
|
||||||
|
|
||||||
|
// Update state and re-render
|
||||||
|
component.hello = false
|
||||||
|
component.requestImmediateUpdate()
|
||||||
|
|
||||||
|
// Verify updated state
|
||||||
|
assertEquals("Good bye", div.querySelector("span")?.textContent)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Additional Development Information
|
||||||
|
|
||||||
|
### Project Structure
|
||||||
|
|
||||||
|
- `src/commonMain` - Common Kotlin code
|
||||||
|
- `src/jsMain` - JavaScript-specific implementation
|
||||||
|
- `src/wasmJsMain` - WebAssembly JavaScript implementation (experimental)
|
||||||
|
- `src/jsTest` - JavaScript tests
|
||||||
|
|
||||||
|
### Key Components
|
||||||
|
|
||||||
|
1. **Komponent** - Base class for all UI components
|
||||||
|
- Override `HtmlBuilder.render()` to define the component's UI
|
||||||
|
- Use `requestUpdate()` for scheduled updates
|
||||||
|
- Use `requestImmediateUpdate()` for immediate updates
|
||||||
|
- Override `generateMemoizeHash()` to optimize re-renders
|
||||||
|
|
||||||
|
2. **HtmlBuilder** - Handles DOM creation and updates
|
||||||
|
- Uses a virtual DOM-like approach to update only what changed
|
||||||
|
- Supports including child components with `include()`
|
||||||
|
- Handles attribute and event binding
|
||||||
|
|
||||||
|
3. **ElementExtensions** - Utility functions for DOM manipulation
|
||||||
|
|
||||||
|
### Optimization Features
|
||||||
|
|
||||||
|
- **Memoization**: Components can implement `generateMemoizeHash()` to avoid unnecessary re-renders
|
||||||
|
- **Batched Updates**: Multiple `requestUpdate()` calls are batched for performance
|
||||||
|
- **Efficient DOM Updates**: Only changed elements are updated in the DOM
|
||||||
|
|
||||||
|
### Error Handling
|
||||||
|
|
||||||
|
The library provides detailed error information when rendering fails:
|
||||||
|
- Set custom error handlers with `Komponent.setErrorHandler()`
|
||||||
|
- Enable debug logging with `Komponent.logRenderEvent = true`
|
||||||
|
- Use `debug {}` blocks in render functions for additional validation
|
||||||
|
|
||||||
|
### Code Style
|
||||||
|
|
||||||
|
- Follow Kotlin's official code style (`kotlin.code.style=official`)
|
||||||
|
- Use functional programming patterns where appropriate
|
||||||
|
- Prefer immutable state when possible
|
||||||
|
- Use descriptive names for components and methods
|
||||||
100
src/jsTest/kotlin/nl/astraeus/komp/TestInsert.kt
Normal file
100
src/jsTest/kotlin/nl/astraeus/komp/TestInsert.kt
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
package nl.astraeus.komp
|
||||||
|
|
||||||
|
import kotlinx.browser.document
|
||||||
|
import kotlinx.html.div
|
||||||
|
import kotlinx.html.table
|
||||||
|
import kotlinx.html.tbody
|
||||||
|
import kotlinx.html.td
|
||||||
|
import kotlinx.html.tr
|
||||||
|
import org.w3c.dom.HTMLDivElement
|
||||||
|
import org.w3c.dom.HTMLTableElement
|
||||||
|
import org.w3c.dom.HTMLTableRowElement
|
||||||
|
import org.w3c.dom.HTMLTableCellElement
|
||||||
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
import kotlin.test.assertNotNull
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Komponent that renders a table with rows
|
||||||
|
*/
|
||||||
|
class TableKomponent : Komponent() {
|
||||||
|
val rows = mutableListOf<RowKomponent>()
|
||||||
|
|
||||||
|
fun addRow(text: String) {
|
||||||
|
rows.add(RowKomponent(text))
|
||||||
|
requestImmediateUpdate()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun HtmlBuilder.render() {
|
||||||
|
div {
|
||||||
|
table {
|
||||||
|
tbody {
|
||||||
|
for (row in rows) {
|
||||||
|
include(row)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Komponent that represents a single row in a table
|
||||||
|
*/
|
||||||
|
class RowKomponent(val text: String) : Komponent() {
|
||||||
|
override fun generateMemoizeHash(): Int = text.hashCode()
|
||||||
|
|
||||||
|
override fun HtmlBuilder.render() {
|
||||||
|
tr {
|
||||||
|
td {
|
||||||
|
+text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test class for inserting rows in the DOM with a Komponent
|
||||||
|
*/
|
||||||
|
class TestInsert {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testInsertRow() {
|
||||||
|
// Create a test component
|
||||||
|
val tableComponent = TableKomponent()
|
||||||
|
val div = document.createElement("div") as HTMLDivElement
|
||||||
|
|
||||||
|
// Render it
|
||||||
|
Komponent.create(div, tableComponent)
|
||||||
|
|
||||||
|
// Verify initial state - should be an empty table
|
||||||
|
val table = div.querySelector("table")
|
||||||
|
assertNotNull(table, "Table should be rendered")
|
||||||
|
val initialRows = table.querySelectorAll("tr")
|
||||||
|
assertEquals(0, initialRows.length, "Table should initially have no rows")
|
||||||
|
|
||||||
|
// Add a row and verify it was inserted
|
||||||
|
tableComponent.addRow("First Row")
|
||||||
|
|
||||||
|
// Verify the row was added
|
||||||
|
val rowsAfterFirstInsert = table.querySelectorAll("tr")
|
||||||
|
assertEquals(1, rowsAfterFirstInsert.length, "Table should have one row after insertion")
|
||||||
|
val firstRowCell = table.querySelector("tr td")
|
||||||
|
assertNotNull(firstRowCell, "First row cell should exist")
|
||||||
|
assertEquals("First Row", firstRowCell.textContent, "Row content should match")
|
||||||
|
|
||||||
|
// Add another row and verify it was inserted
|
||||||
|
tableComponent.addRow("Second Row")
|
||||||
|
|
||||||
|
// Verify both rows are present
|
||||||
|
val rowsAfterSecondInsert = table.querySelectorAll("tr")
|
||||||
|
assertEquals(2, rowsAfterSecondInsert.length, "Table should have two rows after second insertion")
|
||||||
|
val allCells = table.querySelectorAll("tr td")
|
||||||
|
assertEquals(2, allCells.length, "Table should have two cells")
|
||||||
|
assertEquals("First Row", allCells.item(0)?.textContent, "First row content should match")
|
||||||
|
assertEquals("Second Row", allCells.item(1)?.textContent, "Second row content should match")
|
||||||
|
|
||||||
|
// Print the DOM tree for debugging
|
||||||
|
println("Table DOM: ${div.printTree()}")
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user