# Kotlin and Java

> The builder functions with enums, and LO-TEK's check in ordinary tests, on the JVM without Node and without JavaScript.

LO-TEK for Kotlin gives you three things: the builder functions from `lt`, where the allowed values are `enum`s; the same check that `lotek check` and the editor use, as an ordinary function in your tests; and the CSS and the web components as a WebJar. Everything runs in the JVM or is served by the app. No Node, no npm, no process to start and nothing to install alongside.

```kotlin title="build.gradle.kts"
dependencies {
    implementation("io.github.markusaugust:lo-tek-interfaces:0.36.0")
}
```

The version is the same as the npm package’s, so a version number means the same LO-TEK in Maven and on npm. But not every npm version goes to Maven Central. If the version above is not [on Central](https://central.sonatype.com/artifact/io.github.markusaugust/lo-tek-interfaces), use the newest one that is. The package has one dependency, `com.dylibso.chicory:runtime`, and requires Java 17 or newer.

## The CSS and the web components

The package is also a [WebJar](https://www.webjars.org): `lotek.css`, the stylesheet for each component and the JavaScript modules for the browser are under `META-INF/resources/webjars/lo-tek/0.36.0/`, with the same paths as in the npm package. Spring Boot and Servlet containers serve that folder without setup. In Ktor it is one line:

```kotlin
routing {
    staticResources("/webjars", "META-INF/resources/webjars")
}
```

`LotekWebJar` has the paths, with the version in them, so the template never points to a different version than the one the package has:

```kotlin
"""
<link rel="stylesheet" href="${LotekWebJar.CSS}">
<script type="module">
  import { defineLt } from "${LotekWebJar.REGISTER}"
  defineLt()
</script>
"""
```

An address from the component pages keeps its path and only changes the beginning. This one:

```html
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@lo-tek/interfaces@0.36.0/src/components/css/button/button.css">
```

becomes `${LotekWebJar.ROOT}/src/components/css/button/button.css`. What is only for Node or React is not included.

## The builder functions

`Lt` has the same builder functions and options as `lt` in TypeScript. Each gives the attributes you put on the element:

```kotlin
import no.lotek.*


val save = Lt.button(variant = ButtonVariant.SECONDARY)


"""<button ${save.toHtml()}>Save draft</button>"""
// <button class="lt-button" data-variant="secondary">Save draft</button>
```

The allowed values are `enum`s, so `ButtonVariant.SECUNDARY` is a compile error, and the IDE shows what exists. Values that recur have one shared name: `Size`, `FieldState` and `RequiredMarker` are used by every builder function that has them. Every `enum` has `value`, the text that ends up in the HTML, and `of(text)`, which gives the value, or `null` for a text from outside.

The result is an `Attributes`, which is an ordinary `Map<String, String>`. It can be given to any template engine. Without a template engine, `toHtml()` writes it out with the values escaped, and `+` combines two sets.

The builder functions that give several sets give a data class with one field per set. `Lt.field` wires the label, control, help text and error text:

```kotlin
val field = Lt.field(id = "email", required = RequiredMarker.TEXT, help = true, error = true, invalid = invalid)


"""
<label ${field.label.toHtml()}>Email address</label>
<input ${(Lt.input(type = InputType.EMAIL, state = field.state) + field.control).toHtml()}>
<p ${(Lt.helpText() + field.help).toHtml()}>We send the receipt here.</p>
<p ${(Lt.errorText() + field.error).toHtml()}>Enter a valid address.</p>
"""
```

A flag such as `hidden` has the empty text as its value, and is written as the name alone.

### Same answers as in TypeScript

The builder functions are generated from the manifest LO-TEK writes from the TypeScript code, and the few where the options interact, such as `field` and `tabs`, are written by hand. All are tested against more than a thousand build cases with the answer from TypeScript. If one builder function gives one attribute differently, the package is not released.

## kotlinx.html

`io.github.markusaugust:lo-tek-kotlinx-html` has one extension, `lt(…)`, which puts the attributes from the builder functions on a tag. It is a separate package, so the main package does not bring kotlinx.html to those who do not use it.

```kotlin
import kotlinx.html.*
import no.lotek.*
import no.lotek.InputType
import no.lotek.html.lt


fun FlowContent.emailField(invalid: Boolean) {
    val field = Lt.field(id = "email", required = RequiredMarker.TEXT, help = true, error = true, invalid = invalid)
    label { lt(field.label); +"Email address" }
    input { lt(Lt.input(type = InputType.EMAIL, state = field.state), field.control); name = "email" }
    p { lt(Lt.helpText(), field.help); +"We send the receipt here." }
    p { lt(Lt.errorText(), field.error); +"Enter a valid address." }
}
```

Several sets are applied in order, and a later one wins, like `{...a} {...b}` in JSX. The classes are added to those the tag has, so `div(classes = "my-own") { lt(Lt.card()) }` gives both. `InputType` is imported on its own, since kotlinx.html has one with the same name.

### With Ktor

```kotlin
routing {
    staticResources("/webjars", "META-INF/resources/webjars")
    get("/application") {
        call.respondHtml {
            head { link(rel = "stylesheet", href = LotekWebJar.CSS) }
            body {
                form(action = "/application", method = FormMethod.post) {
                    emailField(invalid = false)
                    button { lt(Lt.button()); +"Send" }
                }
                script(type = "module") { unsafe { +"import { defineLt } from \"${LotekWebJar.REGISTER}\"; defineLt()" } }
            }
        }
    }
}
```

## Templates: JTE, Thymeleaf and the others

A template cannot call `Lt` for every value, and then the check is what keeps watch. Write the markup as on the component pages, and check what the template gives in a test. With [JTE](https://jte.gg):

```kotlin
@Test
fun `the application is correct markup`() {
    val out = StringOutput()
    templateEngine.render("application.kte", mapOf("invalid" to true), out)
    assertLotek(out.toString())
}
```

The same goes for Thymeleaf, Freemarker and Pebble: render the template to a text, and give it to `assertLotek`. The check also understands the template syntax in the template file itself, such as `${…}`, `th:…` and `@if`, so the [editor](/the-editor.md) can tell you while you type.

## The check in tests

`assertLotek` checks the HTML the server sends, and fails with the findings as its message:

```kotlin
@Test
fun `the form is wired correctly`() {
    assertLotek(app.render("/form"))
}
```

```text
LO-TEK found 2 findings:
12:30: error: for="email" points to the id "email", which does not exist on the page. … [id-not-found]
18:9: warning: The class "lt-buton" does not exist in LO-TEK. Did you mean lt-button? [unknown-class]
```

It throws an `AssertionError`, which JUnit, Kotest and TestNG all understand. By default a whole page is checked: that every id that is pointed to exists, and that every field is wired. If the HTML is a piece of a page, such as a component, `assertLotek(html, fragment = true)` checks only the vocabulary.

It works the same for everything that makes HTML on the JVM: strings, kotlinx.html, Thymeleaf, JTE, Ktor and Spring. The check reads the HTML that comes out, not the code that makes it.

If you need the findings yourself, `Lotek.diagnosePage(html)` and `Lotek.diagnoseMarkup(html)` give them as a list. Each finding has `start` and `end`, `line` and `column`, `severity`, `rule`, `message`, `link` and sometimes a `fix`. The positions are indices in a Kotlin `String`, so `html.substring(finding.start, finding.end)` gives exactly the text the finding is about, also with “æøå” and emoji in front.

A finding you want to keep is suppressed with a comment above the tag:

```html
<!-- lotek-ignore-next unknown-class -->
<div class="lt-experiment">…</div>
```

Without a rule name, the comment applies to all the rules for the next tag.

## The rendered page

With Playwright for Java, the page is checked as the browser has rendered it, with the markup the web components added and the stylesheets it loaded. The package does not depend on Playwright: `assertLotekRendered` takes a function that runs a script on the page.

```kotlin
@Test
fun `the form is right as it is shown`() {
    page.navigate("http://localhost:8080/form")
    assertLotekRendered { script -> page.evaluate(script) }
}
```

It fails on what `assertLotek` fails on, and in addition on a class no stylesheet styles and a value without a rule. If you have the stylesheets as text, `assertLotek(html, css = listOf(stylesheet))` and `Lotek.diagnosePage(html, css)` give the same check without a browser.

If the project has taken over components, `diagnoseMarkup` and `diagnosePage` take the manifest as text in `manifest`, and then check against the same as the Gradle plugin and the editor. Without the option, the manifest in the package applies. `assertLotek` and `assertLotekRendered` do not take it yet.

```kotlin
val manifest = File("build/lotek/manifest.json").readText()
assertEquals(emptyList(), Lotek.diagnosePage(html, manifest = manifest))
```

The file is written by `lotekManifest`, which is run by `assemble` and not by `test`. If the tests are to read it, they must wait for the task: `tasks.test { dependsOn("lotekManifest") }`.

## The theme

`Lotek.buildTheme(recipe)` builds the same colour theme as [`lotek theme`](/command-line.md#theme), from the JSON text of a recipe file, and `Lotek.inspectTheme(css)` checks a theme you have written yourself, like `lotek check-theme`. The recipe has the nine colours, `typography` and `shape`:

```kotlin
val theme = Lotek.buildTheme("""{"accent": "#7c3aed", "danger": "#b3261e"}""")
File("build/lotek/theme.css").writeText(theme.css)
```

The result has `light` and `dark`, each `--lt-color-*` token with its value, `violations` and `css`. The promises hold by construction, so `violations` is empty for valid colours, but check it before you write the file: `lotek theme` refuses to write when it is not empty, while `buildTheme` returns the theme with the list, as in TypeScript. A key that does not exist, and a recipe the core rejects, throw `IllegalArgumentException` with the explanation. `inspectTheme` gives `problems` with selector and message, and the numbers `blocks`, `declarations` and `promises`, which say how much was actually read. The recipe is read the way `lotek theme` reads it, and both give the answer the core gives in TypeScript, tested against the same expected results.

## The Gradle plugin

The template files themselves can be checked as part of the build, without a test that renders them. The plugin runs the same command line as `lotek check` and `lotek theme`, in the Gradle process:

```kotlin title="build.gradle.kts"
plugins {
    id("io.github.markusaugust.lo-tek") version "0.36.0"
}


lotek {
    templates.from(fileTree("src/main/jte"))
    themeRecipe = file("lotek.theme.json")
}
```

| Task | Does |
| - | - |
| `lotekCheck` | checks `templates` like `lotek check`, and fails with the findings. Run by `check`. |
| `lotekTheme` | builds the theme from `themeRecipe` to `themeOutput`, by default `build/lotek/theme.css` |
| `lotekManifest` | writes the manifest the project is checked against to `build/lotek/manifest.json` when `manifests` has fragments, and deletes the file when it has none. Run by `assemble`. |

```text
> Task :lotekCheck FAILED
The markup does not match LO-TEK:


src/main/jte/application.jte:14:18: warning: The class "lt-buton" does not exist in LO-TEK. Did you mean lt-button?
```

`rendered = true` checks the files as whole pages, with the wiring, like `--rendered`. If you have taken over a component with [`lotek takeover`](/command-line.md#takeover), you put the fragment in `manifests`, so the markup for the copy is checked too, and the copy is included in `build/lotek/manifest.json`:

```kotlin
lotek {
    templates.from(fileTree("src/main/jte"))
    manifests.from("src/main/lotek/button/lotek-manifest.json")
}
```

The plugin is on Maven Central together with the package, with the same version. Gradle finds it there without a separate `pluginManagement` block, since the Gradle Plugin Portal forwards to Maven Central for what it does not have itself. The tasks work with the configuration cache, and are up to date when neither the templates nor the fragments have changed.

## How it works

The check is written in Rust and built to one WebAssembly module. The same module runs in Node, in VS Code and here. On the JVM it is run by [Chicory](https://chicory.dev), a WebAssembly runtime written in pure Java. The module was compiled to JVM bytecode when the package was built, so it runs with the JIT and starts in a fraction of a second. A call borrows an instance from a pool and returns it afterwards, so tests that run at the same time do not wait for each other.

## In IntelliJ

The plugin [LO-TEK Interfaces](https://plugins.jetbrains.com/plugin/34764-lotek) runs the same check while you type, also in HTML in a Kotlin string marked with `@Language("HTML")` or `// language=HTML`. See [The editor](/the-editor.md#jetbrains).
