All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.PreparedSuite.kt Maven / Gradle / Ivy

package opensavvy.prepared.runner.kotest

import io.kotest.core.names.TestName
import io.kotest.core.spec.KotestTestScope
import io.kotest.core.spec.style.StringSpec
import io.kotest.core.spec.style.scopes.ContainerScope
import io.kotest.core.spec.style.scopes.RootScope
import io.kotest.core.spec.style.scopes.addTest
import io.kotest.core.test.TestType
import opensavvy.prepared.suite.SuiteDsl
import opensavvy.prepared.suite.TestDsl
import opensavvy.prepared.suite.config.*
import kotlin.coroutines.CoroutineContext

/**
 * Executes a Prepared [SuiteDsl] in a Kotest suite.
 *
 * Since Kotest cannot represent nested non-suspending test suites, all nested suites are declared at the top-level
 * of the provided [RootScope]. The name of the suite is appended at the start of the tests, so the suite structure is not lost.
 *
 * ### Example
 *
 * This example uses [StringSpec], but tests can be registered using any spec.
 *
 * ```kotlin
 * class MyTests : StringSpec({
 *     "A regular Kotest test" {
 *         // …
 *     }
 *
 *     preparedSuite {
 *         test("A regular Prepared test") {
 *             // …
 *         }
 *
 *         suite("A regular Prepared test suite") {
 *             // …
 *         }
 *     }
 * })
 * ```
 */
@KotestTestScope
fun RootScope.preparedSuite(
	config: TestConfig = TestConfig.Empty,
	block: SuiteDsl.() -> Unit,
) {
	NonNestedSuite(this, config).block()
}

private class NonNestedSuite(private val root: RootScope, private val parentConfig: TestConfig, private val prefix: String? = null) : SuiteDsl {
	override fun suite(name: String, config: TestConfig, block: SuiteDsl.() -> Unit) {
		NonNestedSuite(root, parentConfig + config, prefix child name).block()
	}

	override fun test(name: String, context: CoroutineContext, config: TestConfig, block: suspend TestDsl.() -> Unit) {
		val thisConfig = parentConfig + config

		val kotestConfig = io.kotest.core.test.config.TestConfig(
			enabled = thisConfig[Ignored] == null,
			tags = config[Tag]
				.mapTo(HashSet()) { io.kotest.core.Tag(it.name) }
				.takeIf { it.isNotEmpty() },
			coroutineTestScope = true,
			coroutineDebugProbes = true,
		)

		root.addTest(testName = TestName(name = prefix child name), disabled = false, type = TestType.Test, config = kotestConfig) {
			executeTest(name, context, config, block)
		}
	}
}

// Workaround to avoid using the coroutine dispatcher on KJS.
// See https://gitlab.com/opensavvy/groundwork/prepared/-/issues/59
internal expect suspend fun ContainerScope.executeTest(name: String, context: CoroutineContext, config: TestConfig, block: suspend TestDsl.() -> Unit)

/**
 * Appends [name] at the end of `this`, handling the case where `this` is `null`.
 */
private infix fun String?.child(name: String) =
	when {
		// See https://kotest.io/docs/framework/conditional/conditional-tests-with-focus-and-bang.html
		this != null && name.startsWith("f:") -> "f:$this • $name"
		this != null && name.startsWith("!") -> "!$this • $name"
		this != null -> "$this • $name"
		else -> name
	}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy