diff --git a/docs/settings.gradle.kts b/docs/settings.gradle.kts index 92c69ca..647413d 100644 --- a/docs/settings.gradle.kts +++ b/docs/settings.gradle.kts @@ -60,3 +60,7 @@ include( "example:example-core", "example:example-app", ) + +include("example:example-test") +val tests = project(":example:example-test") +tests.projectDir = file("../test-data") diff --git a/docs/website/build.gradle.kts b/docs/website/build.gradle.kts index b0eeb83..700ae74 100644 --- a/docs/website/build.gradle.kts +++ b/docs/website/build.gradle.kts @@ -6,4 +6,5 @@ plugins { dependencies { dokka(projects.example.exampleCore) dokka(projects.example.exampleApp) + dokka(projects.example.exampleTest) } diff --git a/settings.gradle.kts b/settings.gradle.kts index d8ba10e..02a0484 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -63,6 +63,8 @@ include( "renderer", "dokka-mkdocs", + "test-data", + "gradle:templates:template-app", "gradle:templates:template-lib", ) diff --git a/test-data/README.md b/test-data/README.md new file mode 100644 index 0000000..94c222f --- /dev/null +++ b/test-data/README.md @@ -0,0 +1,5 @@ +# Module Test module + +This module contains declarations that are used as regression tests for the plugin. + +Each package is a test. diff --git a/test-data/build.gradle.kts b/test-data/build.gradle.kts new file mode 100644 index 0000000..edafc9d --- /dev/null +++ b/test-data/build.gradle.kts @@ -0,0 +1,20 @@ +plugins { + kotlin("multiplatform") + id("dev.opensavvy.dokka-mkdocs") +} + +kotlin { + jvm() + js(IR) { + browser() + nodejs() + } +} + +dokka { + moduleName.set("Test module") + + dokkaSourceSets.configureEach { + includes.from("README.md") + } +} diff --git a/test-data/src/commonMain/kotlin/Marker.kt b/test-data/src/commonMain/kotlin/Marker.kt new file mode 100644 index 0000000..9effbcb --- /dev/null +++ b/test-data/src/commonMain/kotlin/Marker.kt @@ -0,0 +1,3 @@ +package opensavvy.dokka.material.mkdocs.test + +typealias Marker = String -- 2.51.2 From 895bfa165f9010d11aba09bdb56fd9ab6bdd13c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20=E2=80=9CCLOVIS=E2=80=9D=20Canet?= Date: Sat, 2 May 2026 17:34:58 +0200 Subject: [PATCH 2/4] test(test-runner): Create the :test-runner module, detect test cases --- settings.gradle.kts | 1 + .../kotlin/helloworld/HelloWorld.kt | 6 +++++ test-runner/README.md | 6 +++++ test-runner/build.gradle.kts | 15 +++++++++++ .../src/jvmTest/kotlin/FindTestCases.kt | 26 +++++++++++++++++++ test-runner/src/jvmTest/kotlin/Runner.kt | 18 +++++++++++++ 6 files changed, 72 insertions(+) create mode 100644 test-data/src/commonMain/kotlin/helloworld/HelloWorld.kt create mode 100644 test-runner/README.md create mode 100644 test-runner/build.gradle.kts create mode 100644 test-runner/src/jvmTest/kotlin/FindTestCases.kt create mode 100644 test-runner/src/jvmTest/kotlin/Runner.kt diff --git a/settings.gradle.kts b/settings.gradle.kts index 02a0484..ad74d1d 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -64,6 +64,7 @@ include( "dokka-mkdocs", "test-data", + "test-runner", "gradle:templates:template-app", "gradle:templates:template-lib", diff --git a/test-data/src/commonMain/kotlin/helloworld/HelloWorld.kt b/test-data/src/commonMain/kotlin/helloworld/HelloWorld.kt new file mode 100644 index 0000000..b2f1a92 --- /dev/null +++ b/test-data/src/commonMain/kotlin/helloworld/HelloWorld.kt @@ -0,0 +1,6 @@ +package opensavvy.dokka.material.mkdocs.test.helloworld + +/** + * This is the simplest possible Kotlin file: just a top-level constant with its documentation. + */ +const val HelloWorld = "Hello, world!" diff --git a/test-runner/README.md b/test-runner/README.md new file mode 100644 index 0000000..6bad79b --- /dev/null +++ b/test-runner/README.md @@ -0,0 +1,6 @@ +# Integration tests + +This module works alongside the [test-data](../test-data) module. + +- `test-data` contains real declarations. +- `test-runner` contains test code. diff --git a/test-runner/build.gradle.kts b/test-runner/build.gradle.kts new file mode 100644 index 0000000..87907e2 --- /dev/null +++ b/test-runner/build.gradle.kts @@ -0,0 +1,15 @@ +plugins { + alias(opensavvyConventions.plugins.base) + alias(opensavvyConventions.plugins.kotlin.internal) + alias(libsCommon.plugins.testBalloon) +} + +kotlin { + jvm() + + sourceSets.jvmTest.dependencies { + implementation(libsCommon.bundles.testBalloon) + implementation(libsCommon.opensavvy.prepared.filesystem) + implementation(libsCommon.opensavvy.prepared.gradle) + } +} diff --git a/test-runner/src/jvmTest/kotlin/FindTestCases.kt b/test-runner/src/jvmTest/kotlin/FindTestCases.kt new file mode 100644 index 0000000..8df1b7a --- /dev/null +++ b/test-runner/src/jvmTest/kotlin/FindTestCases.kt @@ -0,0 +1,26 @@ +package opensavvy.dokka.material.mkdocs.test + +import java.io.File + +fun findTestCases(): List { + val srcFolder = File("../test-data/src").absoluteFile + check(srcFolder.exists()) + check(srcFolder.isDirectory) + + val platformFolders = srcFolder.listFiles() + checkNotNull(platformFolders) { "No platform folders found in test data source" } + + val commonMain = platformFolders.firstOrNull { it.name == "commonMain" } + checkNotNull(commonMain) { "No commonMain folder found in test data source: ${srcFolder}/commonMain" } + + val testCases = platformFolders + .map { File(it, "kotlin") } + .flatMap { + it.listFiles()?.asList().orEmpty() + } + .filter { it.isDirectory } + .distinctBy { it.name } + check(testCases.isNotEmpty()) { "No test cases were found" } + + return testCases +} diff --git a/test-runner/src/jvmTest/kotlin/Runner.kt b/test-runner/src/jvmTest/kotlin/Runner.kt new file mode 100644 index 0000000..12b4732 --- /dev/null +++ b/test-runner/src/jvmTest/kotlin/Runner.kt @@ -0,0 +1,18 @@ +package opensavvy.dokka.material.mkdocs.test + +import opensavvy.prepared.runner.testballoon.preparedSuite + +val IntegrationTests by preparedSuite { + + val cases = findTestCases() + + test("At least one test was found") { + check(cases.isNotEmpty()) { "No integration tests were found, maybe something went wrong?" } + } + + for (case in cases) { + test("Test case: ${case.name}") { + check(case.isDirectory) + } + } +} -- 2.51.2 From 8d1f926c981c369d4ff8bd86c64b611c68868cb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20=E2=80=9CCLOVIS=E2=80=9D=20Canet?= Date: Sat, 2 May 2026 18:07:20 +0200 Subject: [PATCH 3/4] test(test-runner): Generate the documentation & verify against a reference file --- .../kotlin/helloworld/-hello-world.md | 25 ++++++++ .../src/jvmTest/kotlin/GenerateDocs.kt | 62 +++++++++++++++++++ test-runner/src/jvmTest/kotlin/Runner.kt | 31 ++++++++++ 3 files changed, 118 insertions(+) create mode 100644 test-data/src/commonMain/kotlin/helloworld/-hello-world.md create mode 100644 test-runner/src/jvmTest/kotlin/GenerateDocs.kt diff --git a/test-data/src/commonMain/kotlin/helloworld/-hello-world.md b/test-data/src/commonMain/kotlin/helloworld/-hello-world.md new file mode 100644 index 0000000..d691c25 --- /dev/null +++ b/test-data/src/commonMain/kotlin/helloworld/-hello-world.md @@ -0,0 +1,25 @@ +--- +tags: + - common +title: "HelloWorld" +--- + +# HelloWorld + +
+ +
+ +
const val HelloWorld: String
+
+
+ + +
+ +This is the simplest possible Kotlin file: just a top-level constant with its documentation. + + +
+ +
diff --git a/test-runner/src/jvmTest/kotlin/GenerateDocs.kt b/test-runner/src/jvmTest/kotlin/GenerateDocs.kt new file mode 100644 index 0000000..42fe146 --- /dev/null +++ b/test-runner/src/jvmTest/kotlin/GenerateDocs.kt @@ -0,0 +1,62 @@ +package opensavvy.dokka.material.mkdocs.test + +import opensavvy.prepared.compat.filesystem.div +import opensavvy.prepared.suite.shared +import org.gradle.testkit.runner.GradleRunner +import java.io.File +import java.nio.file.Files + +val generateDocs by shared { + val workDir = Files.createTempDirectory("dokka-gradle-test").toFile() + println("Working directory: $workDir") + + val originalWorkDir = File("..") + + (originalWorkDir / "test-data").copyRecursively(workDir / "test-data") + (originalWorkDir / "gradle").copyRecursively(workDir / "gradle") + + (workDir / "settings.gradle.kts").writeText(""" + rootProject.name = "example-test" + + dependencyResolutionManagement { + repositories { + mavenCentral() + } + + versionCatalogs { + create("libsCommon") { + from(files("gradle/common.versions.toml")) + } + } + } + + include("test-data") + include("website") + includeBuild("${File("..").absoluteFile}") + """.trimIndent()) + + (workDir / "build.gradle.kts").writeText(""" + plugins { + kotlin("multiplatform") version libsCommon.versions.kotlin.get() apply false + } + """.trimIndent()) + + (workDir / "website").mkdir() + (workDir / "website" / "build.gradle.kts").writeText(""" + plugins { + id("dev.opensavvy.dokka-mkdocs") + } + + dependencies { + dokka(project(":test-data")) + } + """.trimIndent()) + + GradleRunner.create() + .withGradleVersion("9.4.1") + .withProjectDir(workDir) + .withArguments(":website:dokkaCopyIntoMkDocs") + .build() + + workDir / "website" / "docs" / "api" +} diff --git a/test-runner/src/jvmTest/kotlin/Runner.kt b/test-runner/src/jvmTest/kotlin/Runner.kt index 12b4732..54b0a70 100644 --- a/test-runner/src/jvmTest/kotlin/Runner.kt +++ b/test-runner/src/jvmTest/kotlin/Runner.kt @@ -1,6 +1,10 @@ package opensavvy.dokka.material.mkdocs.test +import opensavvy.prepared.compat.filesystem.div import opensavvy.prepared.runner.testballoon.preparedSuite +import opensavvy.prepared.suite.config.CoroutineTimeout +import kotlin.test.assertEquals +import kotlin.time.Duration.Companion.minutes val IntegrationTests by preparedSuite { @@ -10,9 +14,36 @@ val IntegrationTests by preparedSuite { check(cases.isNotEmpty()) { "No integration tests were found, maybe something went wrong?" } } + test("Generate the documentation", CoroutineTimeout(10.minutes)) { + val docs = generateDocs() + + check(docs.isDirectory) { "No documentation directory was generated." } + check(docs.listFiles() != null) { "No documentation files were generated." } + } + for (case in cases) { test("Test case: ${case.name}") { check(case.isDirectory) + + val outputDirectory = generateDocs() / "test-data" / "opensavvy.dokka.material.mkdocs.test.${case.name}" + check(outputDirectory.isDirectory) { "No output directory was generated for test case" } + println("» Generated in: ${outputDirectory.absolutePath}") + + val markdownFiles = case.listFiles { _, name -> name.endsWith(".md") } + + for (file in markdownFiles) { + println("» Checking: ${file.name}") + + val expected = file.readText() + val actualFile = outputDirectory / file.name + val actual = actualFile.readText() + + assertEquals( + expected = expected, + actual = actual, + message = "Found a difference between the reference file file://${file.absolutePath} and the generated file file://${actualFile.absolutePath}" + ) + } } } } -- 2.51.2 From b1ad9e29bec57c83668c4086dbe025336ccae79c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20=E2=80=9CCLOVIS=E2=80=9D=20Canet?= Date: Sat, 2 May 2026 18:41:27 +0200 Subject: [PATCH 4/4] test(test-runner): Test all Markdown files in the :test-data modules --- .../src/jvmTest/kotlin/FindTestCases.kt | 37 ++++++++++++++---- test-runner/src/jvmTest/kotlin/Runner.kt | 38 ++++++++++--------- 2 files changed, 50 insertions(+), 25 deletions(-) diff --git a/test-runner/src/jvmTest/kotlin/FindTestCases.kt b/test-runner/src/jvmTest/kotlin/FindTestCases.kt index 8df1b7a..dbd4bf3 100644 --- a/test-runner/src/jvmTest/kotlin/FindTestCases.kt +++ b/test-runner/src/jvmTest/kotlin/FindTestCases.kt @@ -1,8 +1,9 @@ package opensavvy.dokka.material.mkdocs.test +import opensavvy.prepared.compat.filesystem.div import java.io.File -fun findTestCases(): List { +fun findTestCases(): List { val srcFolder = File("../test-data/src").absoluteFile check(srcFolder.exists()) check(srcFolder.isDirectory) @@ -10,17 +11,37 @@ fun findTestCases(): List { val platformFolders = srcFolder.listFiles() checkNotNull(platformFolders) { "No platform folders found in test data source" } - val commonMain = platformFolders.firstOrNull { it.name == "commonMain" } - checkNotNull(commonMain) { "No commonMain folder found in test data source: ${srcFolder}/commonMain" } - val testCases = platformFolders .map { File(it, "kotlin") } - .flatMap { - it.listFiles()?.asList().orEmpty() + .flatMap { kotlinFolder -> + kotlinFolder.findMarkdownRecursively().map { child -> + TestCase(child.absoluteFile, child.relativeTo(kotlinFolder)) + } } - .filter { it.isDirectory } - .distinctBy { it.name } check(testCases.isNotEmpty()) { "No test cases were found" } return testCases } + +data class TestCase( + val absolute: File, + val relative: File, +) + +private fun File.findMarkdownRecursively(): List { + if (!isDirectory) + return emptyList() + + val files = listFiles() + ?: return emptyList() + + return files.filter { it.isFile && it.extension == "md" } + + files.filter { it.isDirectory }.flatMap { it.findMarkdownRecursively() } +} + +fun File.root(): File = + parentFile?.root() ?: this + +fun File.replaceRoot(by: File): File = + if (parentFile == null) by + else parentFile!!.replaceRoot(by) / this.name diff --git a/test-runner/src/jvmTest/kotlin/Runner.kt b/test-runner/src/jvmTest/kotlin/Runner.kt index 54b0a70..99b083a 100644 --- a/test-runner/src/jvmTest/kotlin/Runner.kt +++ b/test-runner/src/jvmTest/kotlin/Runner.kt @@ -3,6 +3,7 @@ package opensavvy.dokka.material.mkdocs.test import opensavvy.prepared.compat.filesystem.div import opensavvy.prepared.runner.testballoon.preparedSuite import opensavvy.prepared.suite.config.CoroutineTimeout +import java.io.File import kotlin.test.assertEquals import kotlin.time.Duration.Companion.minutes @@ -22,28 +23,31 @@ val IntegrationTests by preparedSuite { } for (case in cases) { - test("Test case: ${case.name}") { - check(case.isDirectory) + test("Test case: ${case.relative}") { + val generated = generateDocs() - val outputDirectory = generateDocs() / "test-data" / "opensavvy.dokka.material.mkdocs.test.${case.name}" - check(outputDirectory.isDirectory) { "No output directory was generated for test case" } - println("» Generated in: ${outputDirectory.absolutePath}") + val packageCase = "opensavvy.dokka.material.mkdocs.test.${case.relative.root().name}" + println("» Package: $packageCase") + + val generatedRelativePath = case.relative.replaceRoot(File(packageCase)) + println("» Relative: ${case.relative}") + println(" $generatedRelativePath") - val markdownFiles = case.listFiles { _, name -> name.endsWith(".md") } + val outputDirectory = generated / "test-data" + check(outputDirectory.isDirectory) { "No output directory was generated for test case" } + val actualFile = outputDirectory.resolve(case.relative.replaceRoot(File(packageCase))) - for (file in markdownFiles) { - println("» Checking: ${file.name}") + println("» Output: file://${outputDirectory.absolutePath}") + println("» Expected: file://${case.absolute}") + println("» Generated: file://${actualFile.absolutePath}") - val expected = file.readText() - val actualFile = outputDirectory / file.name - val actual = actualFile.readText() + val expected = case.absolute.readText() + val actual = actualFile.readText() - assertEquals( - expected = expected, - actual = actual, - message = "Found a difference between the reference file file://${file.absolutePath} and the generated file file://${actualFile.absolutePath}" - ) - } + assertEquals( + expected = expected, + actual = actual, + ) } } }