Tester Kotlin / JS: frameworks, coroutines et tout-tout-tout

Kotlin est un projet brillant. Initialement conçu comme un simple langage JVM, il a ensuite reçu un support de compilation pour toutes les plates-formes grand public, y compris JavaScript.





Introduction. J'ai un projet animalier - un site et une plate-forme API pour la communauté pour le jeu Elite: Dangerous. Le backend est Kotlin / JVM (Ktor + Hibernate), le frontend est Kotlin / JS (KVision + Fomantic UI). Je vous parlerai du projet pour animaux de compagnie un peu plus tard, et plus sur le front.





  • KVision est un framework frontal pour Kotlin qui combine des idées de différents frameworks de bureau (de Swing et JavaFX à WinForms et Flutter) et des capacités de syntaxe Kotlin, telles que les générateurs DSL.





  • Fomantic-UI est un fork de Semantic-UI, un framework web HTML / JS qui peut être comparé à Bootstrap, seul Fomantic est plus intéressant.





Il n'y a pas si longtemps, j'ai eu l'idée de relier ces deux mondes et d'écrire une bibliothèque pour KVision, ce qui faciliterait au moins l'écriture de pages KVision avec des éléments Fomantic. Et, comme il sied à un projet open source, j'ai prévu de couvrir la bibliothèque de tests. Cet article portera sur cette aventure.





Le code

Tout d'abord, définissons la tâche. Nous avons le code suivant pour un simple bouton Fomantic entre nos mains:





package com.github.kam1sh.kvision.fomantic

import pl.treksoft.kvision.html.*
import pl.treksoft.kvision.panel.SimplePanel


open class FoButton(text: String) : Button(text = text, classes = setOf("ui", "button")) {
    var primary: Boolean = false
        set(value) {
            if (value) addCssClass("primary") else removeCssClass("primary")
            field = value
        }
}

fun SimplePanel.foButton(
    text: String,
    init: (FoButton.() -> Unit)? = null
): FoButton {
    val btn = FoButton(text)
    init?.invoke(btn)
    add(btn)
    return btn
}    

      
      



Et il y a quelques tests:





package com.github.kam1sh.kvision.fomantic

import kotlin.test.BeforeTest
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlinx.browser.document
import kotlinx.coroutines.*
import pl.treksoft.kvision.panel.ContainerType
import pl.treksoft.kvision.panel.Root

class FoButtonTest {
    lateinit var kvapp: Root

    @BeforeTest
    fun before() {
        kvapp = Root("kvapp", containerType = ContainerType.NONE, addRow = false)        
    }

    @Test
    fun genericButtonTest() {
        kvapp.foButton("Button")
        assertEqualsHtml("""...""")
    }

    @Test
    fun buttonPrimaryTest() {
        val btn = kvapp.foButton("Button") { primary = true }
        assertEqualsHtml("""...""")
        btn.primary = false
        assertEqualsHtml("""...""")
    }
}

fun assertEqualsHtml(expected: String, message: String? = null) {
    val actual = document.getElementById("kvapp")?.innerHTML
    assertEquals(expected, actual, message)
}
      
      



: "" KVision div id=kvapp, HTML-.





. div? HTML- - document.body?.insertAdjacentHTML(...)



, , - ?





<source lang="html">
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <script src="https://cdn.jsdelivr.net/npm/jquery@3.3.1/dist/jquery.min.js"></script>
    <link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/fomantic-ui@2.8.7/dist/semantic.min.css">
    <script src="https://cdn.jsdelivr.net/npm/fomantic-ui@2.8.7/dist/semantic.min.js"></script>
</head>
<body>
    <main>
        <div id="kvapp">

        </div>
    </main>
</body>
</html>
</source>
      
      



You've lost karma

Kotlin/JS.

For browser projects, it downloads and installs the Karma test runner with other required dependencies; for Node.js projects, the Mocha test framework is used.

. Karma Mocha. , Karma js- karma.config.d



.





Karma , -:





// karma.config.d/page.js
config.set({
  customContextFile: "../../../../../src/test/resources/test.html"
})
      
      



test.html, , src/test/resources/test.html



. - , Karma build/js/packages/kvision-fomantic-test/node_modules



, .





, ? ./gradlew browserTest



, ... Disconnected (0 times), because no message in 30000 ms.





, HTML- , JS-. build/js/node_modules/karma/static/context.html



.





main-:





<!-- The scripts need to be in the body DOM element, as some test running frameworks need the body
     to have already been created so they can insert their magic into it. For example, if loaded
     before body, Angular Scenario test framework fails to find the body and crashes and burns in
     an epic manner. -->
<script src="context.js"></script>
<script type="text/javascript">
    // Configure our Karma and set up bindings
    %CLIENT_CONFIG%
    window.__karma__.setupContext(window);

    // All served files with the latest timestamps
    %MAPPINGS%
</script>
<!-- Dynamically replaced with <script> tags -->
%SCRIPTS%
<!-- Since %SCRIPTS% might include modules, the `loaded()` call needs to be in a module too.
This ensures all the tests will have been declared before karma tries to run them. -->
<script type="module">
    window.__karma__.loaded();
</script>
<script nomodule>
    window.__karma__.loaded();
</script>
      
      



, ... , .





- . ? , HTTP- Ktor . Python async



, pytest pytest-async, .





suspend .





> Task :compileTestKotlinJs FAILED





e: ...src/test/kotlin/com/github/kam1sh/kvision/fomantic/FoButtonTest.kt: (44, 5): Unsupported [suspend test functions]





- Gradle





. . .





, runBlocking {}



. ...





runBlocking Kotlin/JVM.





, , , , , by design. GlobalScope.promise



, runBlocking :





fun runBlocking(block: suspend (scope: CoroutineScope) -> Unit) = GlobalScope.promise { block(this) }
      
      



. , . Karma :





config.set({
  client: {
    mocha: {
      timeout: 9000
    }
  }
})
      
      



. workaround. UPD: @ilgonmic, , 0. !





Mocha, , , , :





  • , done-, .





  • , Promise.





, , . , kotlin-test-js .

, , . , Promise, Mocha .





, , ? -- ?





- Kotest. .





. , .





// build.gradle.kts
testImplementation("io.kotest:kotest-assertions-core-js:4.3.2")
testImplementation("io.kotest:kotest-framework-api-js:4.3.2")
testImplementation("io.kotest:kotest-framework-engine:4.3.2")
      
      



class FoButtonTest : FunSpec({
    var kvapp: Root? = null
    beforeEach {
        kvapp = Root("kvapp", containerType = ContainerType.NONE, addRow = false)
    }
    test("generic button") {
        kvapp!!.foButton("Button")
        assertEqualsHtml("""...""")
    }

    test("primary button") {
        val btn = kvapp!!.foButton("Button") { primary = true }
        assertEqualsHtml("""...""")
        btn.primary = false
        delay(200)
        assertEqualsHtml("""...""")
    }
})
      
      



kotest , , FunSpec -- .





, , delay() suspend.





:





C'est tout ce que je peux vous dire sur kotest pour le moment. Je continuerai à développer la bibliothèque, et quand elle sera prête, j'annoncerai sa sortie dans la discorde Fomantic et le slack Kotlin / kvision. Et en parallèle j'apprendrai kotest.





Si cela ne vous suffisait pas, alors je m'excuse: je voulais montrer la conclusion ici ./gradlew --debug browserTest



, mais la préparation de ce matériel a déjà été assez retardée en raison de l'apparence de la vie personnelle, donc si vous êtes intéressé, contemplez vous-même les journaux de débogage de Gradle.





Et alors? Eh bien, rien. Mangez Kotlin / JS, buvez du kotest et prenez soin de vous.








All Articles