diff --git a/.idea/modules.xml b/.idea/modules.xml
index 7cefa28aa68bc..0160f9d713fe3 100644
--- a/.idea/modules.xml
+++ b/.idea/modules.xml
@@ -1230,6 +1230,7 @@
+
diff --git a/build/bazel-generated-file-list.txt b/build/bazel-generated-file-list.txt
index 224f96be8aad8..8a930aee48dc0 100644
--- a/build/bazel-generated-file-list.txt
+++ b/build/bazel-generated-file-list.txt
@@ -640,6 +640,7 @@ platform/jewel/markdown/extensions/gfm-tables
platform/jewel/markdown/extensions/images
platform/jewel/markdown/ide-laf-bridge-styling
platform/jewel/markdown/int-ui-standalone-styling
+platform/jewel/markdown/testing
platform/jewel/samples/showcase
platform/jewel/samples/standalone
platform/jewel/ui
diff --git a/platform/jewel/markdown/core/BUILD.bazel b/platform/jewel/markdown/core/BUILD.bazel
index 82a87b267760b..69a082abe8ae4 100644
--- a/platform/jewel/markdown/core/BUILD.bazel
+++ b/platform/jewel/markdown/core/BUILD.bazel
@@ -73,6 +73,8 @@ jvm_library(
"//libraries/compose-runtime-desktop:compose-runtime-desktop_test_lib",
"//libraries/compose-foundation-desktop-junit",
"//libraries/compose-foundation-desktop-junit:compose-foundation-desktop-junit_test_lib",
+ "//platform/jewel/markdown/testing",
+ "//platform/jewel/markdown/testing:testing_test_lib",
"//libraries/jsoup",
"//libraries/jsoup:jsoup_test_lib",
],
diff --git a/platform/jewel/markdown/core/build.gradle.kts b/platform/jewel/markdown/core/build.gradle.kts
index 4cfcd8d310818..0c568a9fe9fb8 100644
--- a/platform/jewel/markdown/core/build.gradle.kts
+++ b/platform/jewel/markdown/core/build.gradle.kts
@@ -12,6 +12,7 @@ dependencies {
testImplementation(compose.desktop.uiTestJUnit4)
testImplementation(projects.ui)
+ testImplementation(projects.markdown.testing)
testImplementation(compose.desktop.currentOs)
}
diff --git a/platform/jewel/markdown/core/intellij.platform.jewel.markdown.core.iml b/platform/jewel/markdown/core/intellij.platform.jewel.markdown.core.iml
index e9fecaf776d65..5c268262c7b7e 100644
--- a/platform/jewel/markdown/core/intellij.platform.jewel.markdown.core.iml
+++ b/platform/jewel/markdown/core/intellij.platform.jewel.markdown.core.iml
@@ -60,6 +60,7 @@
+
\ No newline at end of file
diff --git a/platform/jewel/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/rendering/DefaultMarkdownBlockRenderer.kt b/platform/jewel/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/rendering/DefaultMarkdownBlockRenderer.kt
index 5b7b11bcab651..dc478465a212b 100644
--- a/platform/jewel/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/rendering/DefaultMarkdownBlockRenderer.kt
+++ b/platform/jewel/markdown/core/src/main/kotlin/org/jetbrains/jewel/markdown/rendering/DefaultMarkdownBlockRenderer.kt
@@ -752,7 +752,7 @@ public open class DefaultMarkdownBlockRenderer(
enabled: Boolean,
onUrlClick: ((String) -> Unit)? = null,
) =
- remember(block.inlineContent, styling, enabled) {
+ remember(block.inlineContent, styling, enabled, onUrlClick) {
inlineRenderer.renderAsAnnotatedString(block.inlineContent, styling, enabled, onUrlClick)
}
diff --git a/platform/jewel/markdown/core/src/test/kotlin/org/jetbrains/jewel/markdown/rendering/DefaultMarkdownBlockRendererTest.kt b/platform/jewel/markdown/core/src/test/kotlin/org/jetbrains/jewel/markdown/rendering/DefaultMarkdownBlockRendererTest.kt
new file mode 100644
index 0000000000000..7f9e340302ddb
--- /dev/null
+++ b/platform/jewel/markdown/core/src/test/kotlin/org/jetbrains/jewel/markdown/rendering/DefaultMarkdownBlockRendererTest.kt
@@ -0,0 +1,139 @@
+// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the
+// Apache 2.0 license.
+package org.jetbrains.jewel.markdown.rendering
+
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import androidx.compose.ui.test.runComposeUiTest
+import org.jetbrains.jewel.markdown.processing.MarkdownProcessor
+import org.jetbrains.jewel.markdown.testing.MarkdownTestTheme
+import org.jetbrains.jewel.markdown.testing.createMarkdownTestStyling
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Test
+
+@OptIn(ExperimentalTestApi::class)
+public class DefaultMarkdownBlockRendererTest {
+ @Test
+ public fun `onUrlClick callback updates when changed`() {
+ runComposeUiTest {
+ val processor = MarkdownProcessor()
+ val markdown = "[Click me](https://example.com)"
+ val blocks = processor.processMarkdownDocument(markdown)
+
+ var clickedUrl by mutableStateOf(null)
+ var onUrlClick by mutableStateOf<(String) -> Unit>({ url -> clickedUrl = "first:$url" })
+
+ setContent {
+ MarkdownTestTheme {
+ val renderer = DefaultMarkdownBlockRenderer(createMarkdownTestStyling(), emptyList())
+ renderer.render(
+ blocks,
+ enabled = true,
+ onUrlClick = onUrlClick,
+ onTextClick = {},
+ modifier = Modifier,
+ )
+ }
+ }
+
+ onNodeWithText("Click me").performClick()
+ waitForIdle()
+ assertEquals("first:https://example.com", clickedUrl)
+
+ // Change the callback
+ clickedUrl = null
+ onUrlClick = { url -> clickedUrl = "second:$url" }
+ waitForIdle()
+
+ onNodeWithText("Click me").performClick()
+ waitForIdle()
+ assertEquals("second:https://example.com", clickedUrl)
+ }
+ }
+
+ @Test
+ public fun `enabled change updates link rendering`() {
+ runComposeUiTest {
+ val processor = MarkdownProcessor()
+ val markdown = "[Click me](https://example.com)"
+ val blocks = processor.processMarkdownDocument(markdown)
+
+ var enabled by mutableStateOf(true)
+ var clickedUrl by mutableStateOf(null)
+
+ setContent {
+ MarkdownTestTheme {
+ val renderer = DefaultMarkdownBlockRenderer(createMarkdownTestStyling(), emptyList())
+ renderer.render(
+ blocks,
+ enabled = enabled,
+ onUrlClick = { url -> clickedUrl = url },
+ onTextClick = {},
+ modifier = Modifier,
+ )
+ }
+ }
+
+ // When enabled, clicking the link should invoke the callback
+ onNodeWithText("Click me").performClick()
+ waitForIdle()
+ assertEquals("https://example.com", clickedUrl)
+
+ // Disable and verify the callback is no longer invoked
+ clickedUrl = null
+ enabled = false
+ waitForIdle()
+
+ onNodeWithText("Click me").performClick()
+ waitForIdle()
+ // When disabled, links should not be clickable
+ assertTrue("Link should not be clickable when disabled", clickedUrl == null)
+ }
+ }
+
+ @Test
+ public fun `rendered content updates when both enabled and onUrlClick change simultaneously`() {
+ runComposeUiTest {
+ val processor = MarkdownProcessor()
+ val markdown = "[Click me](https://example.com)"
+ val blocks = processor.processMarkdownDocument(markdown)
+
+ var enabled by mutableStateOf(false)
+ var clickedUrl by mutableStateOf(null)
+ var onUrlClick by mutableStateOf<(String) -> Unit>({ url -> clickedUrl = "first:$url" })
+
+ setContent {
+ MarkdownTestTheme {
+ val renderer = DefaultMarkdownBlockRenderer(createMarkdownTestStyling(), emptyList())
+ renderer.render(
+ blocks,
+ enabled = enabled,
+ onUrlClick = onUrlClick,
+ onTextClick = {},
+ modifier = Modifier,
+ )
+ }
+ }
+
+ // Initially disabled - click should not work
+ onNodeWithText("Click me").performClick()
+ waitForIdle()
+ assertTrue("Link should not be clickable when disabled", clickedUrl == null)
+
+ // Enable and change callback simultaneously
+ enabled = true
+ onUrlClick = { url -> clickedUrl = "second:$url" }
+ waitForIdle()
+
+ onNodeWithText("Click me").performClick()
+ waitForIdle()
+ assertEquals("second:https://example.com", clickedUrl)
+ }
+ }
+}
diff --git a/platform/jewel/markdown/core/src/test/kotlin/org/jetbrains/jewel/markdown/scrolling/ScrollingSynchronizerTest.kt b/platform/jewel/markdown/core/src/test/kotlin/org/jetbrains/jewel/markdown/scrolling/ScrollingSynchronizerTest.kt
index 47e833d607f25..8420fd35b4c7c 100644
--- a/platform/jewel/markdown/core/src/test/kotlin/org/jetbrains/jewel/markdown/scrolling/ScrollingSynchronizerTest.kt
+++ b/platform/jewel/markdown/core/src/test/kotlin/org/jetbrains/jewel/markdown/scrolling/ScrollingSynchronizerTest.kt
@@ -3,41 +3,21 @@
package org.jetbrains.jewel.markdown.scrolling
import androidx.compose.foundation.ScrollState
-import androidx.compose.foundation.layout.PaddingValues
-import androidx.compose.foundation.shape.CornerSize
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.rememberCoroutineScope
-import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.RectangleShape
-import androidx.compose.ui.graphics.StrokeCap
-import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.runComposeUiTest
-import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.TextStyle
-import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.Density
-import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
-import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import org.intellij.lang.annotations.Language
-import org.jetbrains.jewel.foundation.BorderColors
-import org.jetbrains.jewel.foundation.DisabledAppearanceValues
-import org.jetbrains.jewel.foundation.GlobalColors
-import org.jetbrains.jewel.foundation.GlobalMetrics
-import org.jetbrains.jewel.foundation.OutlineColors
-import org.jetbrains.jewel.foundation.TextColors
import org.jetbrains.jewel.foundation.code.highlighting.LocalCodeHighlighter
import org.jetbrains.jewel.foundation.code.highlighting.NoOpCodeHighlighter
import org.jetbrains.jewel.foundation.theme.JewelTheme
-import org.jetbrains.jewel.foundation.theme.ThemeColorPalette
-import org.jetbrains.jewel.foundation.theme.ThemeDefinition
-import org.jetbrains.jewel.foundation.theme.ThemeIconData
import org.jetbrains.jewel.markdown.MarkdownBlock
import org.jetbrains.jewel.markdown.MarkdownMode
import org.jetbrains.jewel.markdown.extensions.LocalMarkdownBlockRenderer
@@ -46,19 +26,13 @@ import org.jetbrains.jewel.markdown.extensions.LocalMarkdownProcessor
import org.jetbrains.jewel.markdown.extensions.LocalMarkdownStyling
import org.jetbrains.jewel.markdown.processing.MarkdownProcessor
import org.jetbrains.jewel.markdown.rendering.DefaultInlineMarkdownRenderer
-import org.jetbrains.jewel.markdown.rendering.InlinesStyling
import org.jetbrains.jewel.markdown.rendering.MarkdownStyling
-import org.jetbrains.jewel.markdown.rendering.MarkdownStyling.List.Ordered.NumberFormatStyles.NumberFormatStyle
-import org.jetbrains.jewel.markdown.rendering.MarkdownStyling.List.Unordered.BulletCharStyles
-import org.jetbrains.jewel.ui.component.styling.DividerMetrics
-import org.jetbrains.jewel.ui.component.styling.DividerStyle
+import org.jetbrains.jewel.markdown.testing.createMarkdownTestDividerStyle
+import org.jetbrains.jewel.markdown.testing.createMarkdownTestScrollbarStyle
+import org.jetbrains.jewel.markdown.testing.createMarkdownTestStyling
+import org.jetbrains.jewel.markdown.testing.createMarkdownTestThemeDefinition
import org.jetbrains.jewel.ui.component.styling.LocalDividerStyle
import org.jetbrains.jewel.ui.component.styling.LocalScrollbarStyle
-import org.jetbrains.jewel.ui.component.styling.ScrollbarColors
-import org.jetbrains.jewel.ui.component.styling.ScrollbarMetrics
-import org.jetbrains.jewel.ui.component.styling.ScrollbarStyle
-import org.jetbrains.jewel.ui.component.styling.ScrollbarVisibility
-import org.jetbrains.jewel.ui.component.styling.TrackClickBehavior
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test
@@ -792,7 +766,8 @@ public class ScrollingSynchronizerTest {
) = runComposeUiTest {
val scrollState = ScrollState(0)
val synchronizer = ScrollingSynchronizer.create(scrollState)!!
- val markdownStyling: MarkdownStyling = createMarkdownStyling()
+ val markdownStyling: MarkdownStyling =
+ createMarkdownTestStyling(codeEditorTextStyle = TextStyle.Default.copy(fontSize = CODE_TEXT_SIZE.sp))
val renderer =
ScrollSyncMarkdownBlockRenderer(markdownStyling, emptyList(), DefaultInlineMarkdownRenderer(emptyList()))
val processor =
@@ -807,11 +782,11 @@ public class ScrollingSynchronizerTest {
LocalMarkdownProcessor provides processor,
LocalMarkdownBlockRenderer provides renderer,
LocalCodeHighlighter provides NoOpCodeHighlighter,
- LocalDividerStyle provides createDividerStyle(),
- LocalScrollbarStyle provides createScrollbarStyle(),
- LocalDensity provides createDensity(),
+ LocalDividerStyle provides createMarkdownTestDividerStyle(),
+ LocalScrollbarStyle provides createMarkdownTestScrollbarStyle(),
+ LocalDensity provides Density(1f),
) {
- JewelTheme(createThemeDefinition()) {
+ JewelTheme(createMarkdownTestThemeDefinition()) {
val blocks = processor.yieldBlocks()
renderer.render(blocks, true, {}, {}, Modifier)
}
@@ -822,201 +797,6 @@ public class ScrollingSynchronizerTest {
waitForIdle()
}
- private fun createDividerStyle() =
- DividerStyle(color = Color.Black, metrics = DividerMetrics(thickness = 1.dp, startIndent = 0.dp))
-
- private fun createScrollbarStyle() =
- ScrollbarStyle(
- colors =
- ScrollbarColors(
- thumbBackground = Color.Black,
- thumbBorderActive = Color.Black,
- thumbBackgroundActive = Color.Black,
- thumbOpaqueBackground = Color.Black,
- thumbOpaqueBackgroundHovered = Color.Black,
- thumbBorder = Color.Black,
- thumbOpaqueBorder = Color.Black,
- thumbOpaqueBorderHovered = Color.Black,
- trackBackground = Color.Black,
- trackBackgroundExpanded = Color.Black,
- trackOpaqueBackground = Color.Black,
- trackOpaqueBackgroundHovered = Color.Black,
- ),
- metrics = ScrollbarMetrics(thumbCornerSize = CornerSize(1.dp), minThumbLength = 1.dp),
- trackClickBehavior = TrackClickBehavior.NextPage,
- scrollbarVisibility =
- ScrollbarVisibility.AlwaysVisible(
- trackThickness = 1.dp,
- trackPadding = PaddingValues(1.dp),
- trackPaddingWithBorder = PaddingValues(1.dp),
- thumbColorAnimationDuration = 500.milliseconds,
- trackColorAnimationDuration = 500.milliseconds,
- scrollbarBackgroundColorLight = Color.White,
- scrollbarBackgroundColorDark = Color.White,
- ),
- )
-
- private fun createDensity() = Density(1f)
-
- private fun createThemeDefinition(): ThemeDefinition {
- return ThemeDefinition(
- name = "Test",
- isDark = false,
- globalColors =
- GlobalColors(
- borders = BorderColors(normal = Color.Black, focused = Color.Black, disabled = Color.Black),
- outlines =
- OutlineColors(
- focused = Color.Black,
- focusedWarning = Color.Black,
- focusedError = Color.Black,
- warning = Color.Black,
- error = Color.Black,
- ),
- text =
- TextColors(
- normal = Color.Black,
- selected = Color.Black,
- disabled = Color.Black,
- disabledSelected = Color.Black,
- info = Color.Black,
- error = Color.Black,
- warning = Color.Black,
- ),
- panelBackground = Color.White,
- toolwindowBackground = Color.White,
- ),
- globalMetrics = GlobalMetrics(outlineWidth = 10.dp, rowHeight = 24.dp),
- defaultTextStyle = TextStyle.Default,
- editorTextStyle = TextStyle.Default,
- consoleTextStyle = TextStyle.Default,
- contentColor = Color.Black,
- colorPalette = ThemeColorPalette.Empty,
- iconData = ThemeIconData.Empty,
- disabledAppearanceValues = DisabledAppearanceValues(brightness = 33, contrast = -35, alpha = 100),
- )
- }
-
- private fun createMarkdownStyling(): MarkdownStyling {
- val mockSpanStyle = SpanStyle(Color.Black)
- val inlinesStyling =
- InlinesStyling(
- textStyle = TextStyle.Default,
- inlineCode = mockSpanStyle,
- link = mockSpanStyle,
- linkDisabled = mockSpanStyle,
- linkFocused = mockSpanStyle,
- linkHovered = mockSpanStyle,
- linkPressed = mockSpanStyle,
- linkVisited = mockSpanStyle,
- emphasis = mockSpanStyle,
- strongEmphasis = mockSpanStyle,
- inlineHtml = mockSpanStyle,
- )
- return MarkdownStyling(
- blockVerticalSpacing = 8.dp,
- paragraph = MarkdownStyling.Paragraph(inlinesStyling),
- heading =
- MarkdownStyling.Heading(
- h1 = MarkdownStyling.Heading.H1(inlinesStyling, 1.dp, Color.Black, 2.dp, PaddingValues(4.dp)),
- h2 = MarkdownStyling.Heading.H2(inlinesStyling, 1.dp, Color.Black, 2.dp, PaddingValues(4.dp)),
- h3 = MarkdownStyling.Heading.H3(inlinesStyling, 1.dp, Color.Black, 2.dp, PaddingValues(4.dp)),
- h4 = MarkdownStyling.Heading.H4(inlinesStyling, 1.dp, Color.Black, 2.dp, PaddingValues(4.dp)),
- h5 = MarkdownStyling.Heading.H5(inlinesStyling, 1.dp, Color.Black, 2.dp, PaddingValues(4.dp)),
- h6 = MarkdownStyling.Heading.H6(inlinesStyling, 1.dp, Color.Black, 2.dp, PaddingValues(4.dp)),
- ),
- blockQuote =
- MarkdownStyling.BlockQuote(
- padding = PaddingValues(4.dp),
- lineWidth = 2.dp,
- lineColor = Color.Gray,
- pathEffect = null,
- strokeCap = StrokeCap.Square,
- textColor = Color.Black,
- ),
- code =
- MarkdownStyling.Code(
- indented =
- MarkdownStyling.Code.Indented(
- editorTextStyle = TextStyle.Default.copy(fontSize = CODE_TEXT_SIZE.sp),
- padding = PaddingValues(4.dp),
- shape = RectangleShape,
- background = Color.LightGray,
- borderWidth = 0.dp,
- borderColor = Color.DarkGray,
- fillWidth = true,
- scrollsHorizontally = true,
- ),
- fenced =
- MarkdownStyling.Code.Fenced(
- editorTextStyle = TextStyle.Default.copy(fontSize = CODE_TEXT_SIZE.sp),
- padding = PaddingValues(4.dp),
- shape = RectangleShape,
- background = Color.LightGray,
- borderWidth = 0.dp,
- borderColor = Color.DarkGray,
- fillWidth = true,
- scrollsHorizontally = true,
- infoTextStyle = TextStyle.Default,
- infoPadding = PaddingValues(2.dp),
- infoPosition = MarkdownStyling.Code.Fenced.InfoPosition.TopStart,
- ),
- ),
- list =
- MarkdownStyling.List(
- ordered =
- MarkdownStyling.List.Ordered(
- numberStyle = TextStyle.Default,
- numberContentGap = 1.dp,
- numberMinWidth = 2.dp,
- numberTextAlign = TextAlign.Start,
- itemVerticalSpacing = 4.dp,
- itemVerticalSpacingTight = 2.dp,
- padding = PaddingValues(4.dp),
- numberFormatStyles =
- MarkdownStyling.List.Ordered.NumberFormatStyles(firstLevel = NumberFormatStyle.Decimal),
- ),
- unordered =
- MarkdownStyling.List.Unordered(
- bullet = '.',
- bulletStyle = TextStyle.Default,
- bulletContentGap = 1.dp,
- itemVerticalSpacing = 4.dp,
- itemVerticalSpacingTight = 2.dp,
- padding = PaddingValues(4.dp),
- markerMinWidth = 16.dp,
- bulletCharStyles = BulletCharStyles(),
- ),
- ),
- image =
- MarkdownStyling.Image(
- alignment = Alignment.Center,
- contentScale = ContentScale.Crop,
- padding = PaddingValues(8.dp),
- shape = RectangleShape,
- background = Color.Transparent,
- borderWidth = 1.dp,
- borderColor = Color.LightGray,
- ),
- thematicBreak =
- MarkdownStyling.ThematicBreak(
- padding = PaddingValues(4.dp),
- lineWidth = 2.dp,
- lineColor = Color.DarkGray,
- ),
- htmlBlock =
- MarkdownStyling.HtmlBlock(
- textStyle = TextStyle.Default,
- padding = PaddingValues(4.dp),
- shape = RectangleShape,
- background = Color.White,
- borderWidth = 1.dp,
- borderColor = Color.Gray,
- fillWidth = true,
- ),
- )
- }
-
public companion object {
private const val CODE_TEXT_SIZE = 10
}
diff --git a/platform/jewel/markdown/extensions/gfm-tables/BUILD.bazel b/platform/jewel/markdown/extensions/gfm-tables/BUILD.bazel
index c75eec00b955a..4d4847e25b5b8 100644
--- a/platform/jewel/markdown/extensions/gfm-tables/BUILD.bazel
+++ b/platform/jewel/markdown/extensions/gfm-tables/BUILD.bazel
@@ -43,21 +43,42 @@ jvm_library(
jvm_library(
name = "gfm-tables_test_lib",
- module_name = "intellij.platform.jewel.markdown.extensions.gfmTables",
visibility = ["//visibility:public"],
- srcs = glob([], allow_empty = True),
+ srcs = glob(["src/test/kotlin/**/*.kt", "src/test/kotlin/**/*.java", "src/test/kotlin/**/*.form"], allow_empty = True),
kotlinc_opts = ":custom_gfm-tables",
- runtime_deps = [
- ":gfm-tables",
+ associates = [":gfm-tables"],
+ deps = [
+ "@lib//:kotlin-stdlib",
+ "//libraries/kotlinx/coroutines/core",
"//libraries/kotlinx/coroutines/core:core_test_lib",
+ "@lib//:jetbrains-annotations",
+ "//platform/jewel/markdown/core",
"//platform/jewel/markdown/core:core_test_lib",
+ "//platform/jewel/ui",
"//platform/jewel/ui:ui_test_lib",
+ "//platform/jewel/foundation",
"//platform/jewel/foundation:foundation_test_lib",
+ "//libraries/compose-foundation-desktop",
"//libraries/compose-foundation-desktop:compose-foundation-desktop_test_lib",
+ "//libraries/compose-runtime-desktop",
"//libraries/compose-runtime-desktop:compose-runtime-desktop_test_lib",
+ "@lib//:platform-jewel-markdown-extensions-gfm_tables-commonmark-ext-gfm-tables",
+ "//libraries/junit4",
+ "//libraries/junit4:junit4_test_lib",
"//libraries/compose-foundation-desktop-junit",
"//libraries/compose-foundation-desktop-junit:compose-foundation-desktop-junit_test_lib",
+ "//platform/jewel/markdown/testing",
+ "//platform/jewel/markdown/testing:testing_test_lib",
],
plugins = ["@lib//:compose-plugin"]
)
-### auto-generated section `build intellij.platform.jewel.markdown.extensions.gfmTables` end
\ No newline at end of file
+### auto-generated section `build intellij.platform.jewel.markdown.extensions.gfmTables` end
+
+### auto-generated section `test intellij.platform.jewel.markdown.extensions.gfmTables` start
+load("@community//build:tests-options.bzl", "jps_test")
+
+jps_test(
+ name = "gfm-tables_test",
+ runtime_deps = [":gfm-tables_test_lib"]
+)
+### auto-generated section `test intellij.platform.jewel.markdown.extensions.gfmTables` end
\ No newline at end of file
diff --git a/platform/jewel/markdown/extensions/gfm-tables/build.gradle.kts b/platform/jewel/markdown/extensions/gfm-tables/build.gradle.kts
index d4d11bd8d5f4c..b97e75d43d317 100644
--- a/platform/jewel/markdown/extensions/gfm-tables/build.gradle.kts
+++ b/platform/jewel/markdown/extensions/gfm-tables/build.gradle.kts
@@ -12,6 +12,8 @@ dependencies {
implementation(libs.commonmark.ext.gfm.tables)
testImplementation(compose.desktop.uiTestJUnit4)
+ testImplementation(projects.markdown.testing)
+ testImplementation(compose.desktop.currentOs)
}
publicApiValidation { excludedClassRegexes = setOf("org.jetbrains.jewel.markdown.extensions.github.tables.*") }
diff --git a/platform/jewel/markdown/extensions/gfm-tables/intellij.platform.jewel.markdown.extensions.gfmTables.iml b/platform/jewel/markdown/extensions/gfm-tables/intellij.platform.jewel.markdown.extensions.gfmTables.iml
index a289c020b4d91..071697c471801 100644
--- a/platform/jewel/markdown/extensions/gfm-tables/intellij.platform.jewel.markdown.extensions.gfmTables.iml
+++ b/platform/jewel/markdown/extensions/gfm-tables/intellij.platform.jewel.markdown.extensions.gfmTables.iml
@@ -26,6 +26,7 @@
+
@@ -57,6 +58,8 @@
+
+
\ No newline at end of file
diff --git a/platform/jewel/markdown/extensions/gfm-tables/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/github/tables/GitHubTableBlockRenderer.kt b/platform/jewel/markdown/extensions/gfm-tables/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/github/tables/GitHubTableBlockRenderer.kt
index 62ec428e1fab8..b79e6248e6f67 100644
--- a/platform/jewel/markdown/extensions/gfm-tables/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/github/tables/GitHubTableBlockRenderer.kt
+++ b/platform/jewel/markdown/extensions/gfm-tables/src/main/kotlin/org/jetbrains/jewel/markdown/extensions/github/tables/GitHubTableBlockRenderer.kt
@@ -74,7 +74,7 @@ internal class GitHubTableBlockRenderer(
val headerRenderer = remember(headerRootStyling) { blockRenderer.createCopy(rootStyling = headerRootStyling) }
val rows =
- remember(tableBlock, blockRenderer, inlineRenderer, tableStyling) {
+ remember(tableBlock, blockRenderer, inlineRenderer, tableStyling, enabled, onUrlClick) {
val headerCells =
tableBlock.header.cells.map Unit> { cell ->
{
diff --git a/platform/jewel/markdown/extensions/gfm-tables/src/test/kotlin/org/jetbrains/jewel/markdown/extensions/github/tables/GitHubTableBlockRendererRememberKeysTest.kt b/platform/jewel/markdown/extensions/gfm-tables/src/test/kotlin/org/jetbrains/jewel/markdown/extensions/github/tables/GitHubTableBlockRendererRememberKeysTest.kt
new file mode 100644
index 0000000000000..a05f964a70ad4
--- /dev/null
+++ b/platform/jewel/markdown/extensions/gfm-tables/src/test/kotlin/org/jetbrains/jewel/markdown/extensions/github/tables/GitHubTableBlockRendererRememberKeysTest.kt
@@ -0,0 +1,143 @@
+// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the
+// Apache 2.0 license.
+package org.jetbrains.jewel.markdown.extensions.github.tables
+
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import androidx.compose.ui.test.runComposeUiTest
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.dp
+import org.jetbrains.jewel.markdown.processing.MarkdownProcessor
+import org.jetbrains.jewel.markdown.rendering.DefaultMarkdownBlockRenderer
+import org.jetbrains.jewel.markdown.testing.MarkdownTestTheme
+import org.jetbrains.jewel.markdown.testing.createMarkdownTestStyling
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Test
+
+@OptIn(ExperimentalTestApi::class)
+public class GitHubTableBlockRendererRememberKeysTest {
+ @Test
+ public fun `onUrlClick callback updates when changed`() {
+ runComposeUiTest {
+ val markdownStyling = createMarkdownTestStyling()
+ val tableStyling = createTableStyling()
+ val rendererExtension = GitHubTableRendererExtension(tableStyling, markdownStyling)
+ val processor = MarkdownProcessor(extensions = listOf(GitHubTableProcessorExtension))
+
+ val markdown =
+ """
+ | Header |
+ | ------ |
+ | [Click me](https://example.com) |
+ """
+ .trimIndent()
+ val blocks = processor.processMarkdownDocument(markdown)
+
+ var clickedUrl by mutableStateOf(null)
+ var onUrlClick by mutableStateOf<(String) -> Unit>({ url -> clickedUrl = "first:$url" })
+
+ setContent {
+ MarkdownTestTheme {
+ val renderer = DefaultMarkdownBlockRenderer(markdownStyling, listOf(rendererExtension))
+ renderer.render(
+ blocks,
+ enabled = true,
+ onUrlClick = onUrlClick,
+ onTextClick = {},
+ modifier = Modifier,
+ )
+ }
+ }
+
+ onNodeWithText("Click me").performClick()
+ waitForIdle()
+ assertEquals("first:https://example.com", clickedUrl)
+
+ // Change the callback
+ clickedUrl = null
+ onUrlClick = { url -> clickedUrl = "second:$url" }
+ waitForIdle()
+
+ onNodeWithText("Click me").performClick()
+ waitForIdle()
+ assertEquals("second:https://example.com", clickedUrl)
+ }
+ }
+
+ @Test
+ public fun `enabled change updates link rendering`() {
+ runComposeUiTest {
+ val markdownStyling = createMarkdownTestStyling()
+ val tableStyling = createTableStyling()
+ val rendererExtension = GitHubTableRendererExtension(tableStyling, markdownStyling)
+ val processor = MarkdownProcessor(extensions = listOf(GitHubTableProcessorExtension))
+
+ val markdown =
+ """
+ | Header |
+ | ------ |
+ | [Click me](https://example.com) |
+ """
+ .trimIndent()
+ val blocks = processor.processMarkdownDocument(markdown)
+
+ var enabled by mutableStateOf(true)
+ var clickedUrl by mutableStateOf(null)
+
+ setContent {
+ MarkdownTestTheme {
+ val renderer = DefaultMarkdownBlockRenderer(markdownStyling, listOf(rendererExtension))
+ renderer.render(
+ blocks,
+ enabled = enabled,
+ onUrlClick = { url -> clickedUrl = url },
+ onTextClick = {},
+ modifier = Modifier,
+ )
+ }
+ }
+
+ // When enabled, clicking the link should invoke the callback
+ onNodeWithText("Click me").performClick()
+ waitForIdle()
+ assertEquals("https://example.com", clickedUrl)
+
+ // Disable and verify the callback is no longer invoked
+ clickedUrl = null
+ enabled = false
+ waitForIdle()
+
+ onNodeWithText("Click me").performClick()
+ waitForIdle()
+ assertTrue("Link should not be clickable when disabled", clickedUrl == null)
+ }
+ }
+
+ private fun createTableStyling() =
+ GfmTableStyling(
+ colors =
+ GfmTableColors(
+ borderColor = Color.Black,
+ rowBackgroundColor = Color.White,
+ alternateRowBackgroundColor = Color.LightGray,
+ rowBackgroundStyle = RowBackgroundStyle.Normal,
+ ),
+ metrics =
+ GfmTableMetrics(
+ borderWidth = 1.dp,
+ cellPadding = PaddingValues(4.dp),
+ defaultCellContentAlignment = Alignment.Start,
+ headerDefaultCellContentAlignment = Alignment.Start,
+ ),
+ headerBaseFontWeight = FontWeight.Bold,
+ )
+}
diff --git a/platform/jewel/markdown/testing/BUILD.bazel b/platform/jewel/markdown/testing/BUILD.bazel
new file mode 100644
index 0000000000000..1db073f35ed39
--- /dev/null
+++ b/platform/jewel/markdown/testing/BUILD.bazel
@@ -0,0 +1,50 @@
+### auto-generated section `build intellij.platform.jewel.markdown.testing` start
+load("//build:compiler-options.bzl", "create_kotlinc_options")
+load("@rules_jvm//:jvm.bzl", "jvm_library")
+
+create_kotlinc_options(
+ name = "custom_testing",
+ opt_in = [
+ "androidx.compose.ui.ExperimentalComposeUiApi",
+ "androidx.compose.foundation.ExperimentalFoundationApi",
+ "org.jetbrains.jewel.foundation.ExperimentalJewelApi",
+ "org.jetbrains.jewel.foundation.InternalJewelApi",
+ ],
+ x_context_parameters = True
+)
+
+jvm_library(
+ name = "testing",
+ module_name = "intellij.platform.jewel.markdown.testing",
+ visibility = ["//visibility:public"],
+ srcs = glob(["src/main/kotlin/**/*.kt", "src/main/kotlin/**/*.java", "src/main/kotlin/**/*.form"], allow_empty = True),
+ kotlinc_opts = ":custom_testing",
+ deps = [
+ "@lib//:kotlin-stdlib",
+ "@lib//:jetbrains-annotations",
+ "//platform/jewel/markdown/core",
+ "//platform/jewel/ui",
+ "//platform/jewel/foundation",
+ "//libraries/compose-foundation-desktop",
+ "//libraries/compose-runtime-desktop",
+ ],
+ plugins = ["@lib//:compose-plugin"]
+)
+
+jvm_library(
+ name = "testing_test_lib",
+ module_name = "intellij.platform.jewel.markdown.testing",
+ visibility = ["//visibility:public"],
+ srcs = glob([], allow_empty = True),
+ kotlinc_opts = ":custom_testing",
+ runtime_deps = [
+ ":testing",
+ "//platform/jewel/markdown/core:core_test_lib",
+ "//platform/jewel/ui:ui_test_lib",
+ "//platform/jewel/foundation:foundation_test_lib",
+ "//libraries/compose-foundation-desktop:compose-foundation-desktop_test_lib",
+ "//libraries/compose-runtime-desktop:compose-runtime-desktop_test_lib",
+ ],
+ plugins = ["@lib//:compose-plugin"]
+)
+### auto-generated section `build intellij.platform.jewel.markdown.testing` end
\ No newline at end of file
diff --git a/platform/jewel/markdown/testing/build.gradle.kts b/platform/jewel/markdown/testing/build.gradle.kts
new file mode 100644
index 0000000000000..0950df1cea7d2
--- /dev/null
+++ b/platform/jewel/markdown/testing/build.gradle.kts
@@ -0,0 +1,9 @@
+plugins {
+ jewel
+ alias(libs.plugins.composeDesktop)
+ alias(libs.plugins.compose.compiler)
+}
+
+dependencies {
+ api(projects.markdown.core)
+}
diff --git a/platform/jewel/markdown/testing/intellij.platform.jewel.markdown.testing.iml b/platform/jewel/markdown/testing/intellij.platform.jewel.markdown.testing.iml
new file mode 100644
index 0000000000000..dc0c903d99350
--- /dev/null
+++ b/platform/jewel/markdown/testing/intellij.platform.jewel.markdown.testing.iml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-compose-compiler-plugin/2.3.10-RC/kotlin-compose-compiler-plugin-2.3.10-RC.jar
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/platform/jewel/markdown/testing/module-content.yaml b/platform/jewel/markdown/testing/module-content.yaml
new file mode 100644
index 0000000000000..ff2558b7e949a
--- /dev/null
+++ b/platform/jewel/markdown/testing/module-content.yaml
@@ -0,0 +1,3 @@
+- name: dist.all/lib/intellij.platform.jewel.markdown.testing.jar
+ modules:
+ - name: intellij.platform.jewel.markdown.testing
diff --git a/platform/jewel/markdown/testing/src/main/kotlin/org/jetbrains/jewel/markdown/testing/MarkdownTestTheme.kt b/platform/jewel/markdown/testing/src/main/kotlin/org/jetbrains/jewel/markdown/testing/MarkdownTestTheme.kt
new file mode 100644
index 0000000000000..98fbb2f0285c1
--- /dev/null
+++ b/platform/jewel/markdown/testing/src/main/kotlin/org/jetbrains/jewel/markdown/testing/MarkdownTestTheme.kt
@@ -0,0 +1,245 @@
+// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the
+// Apache 2.0 license.
+package org.jetbrains.jewel.markdown.testing
+
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.shape.CornerSize
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.RectangleShape
+import androidx.compose.ui.graphics.StrokeCap
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.text.SpanStyle
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.dp
+import kotlin.time.Duration.Companion.milliseconds
+import org.jetbrains.jewel.foundation.BorderColors
+import org.jetbrains.jewel.foundation.DisabledAppearanceValues
+import org.jetbrains.jewel.foundation.GlobalColors
+import org.jetbrains.jewel.foundation.GlobalMetrics
+import org.jetbrains.jewel.foundation.OutlineColors
+import org.jetbrains.jewel.foundation.TextColors
+import org.jetbrains.jewel.foundation.code.highlighting.LocalCodeHighlighter
+import org.jetbrains.jewel.foundation.code.highlighting.NoOpCodeHighlighter
+import org.jetbrains.jewel.foundation.theme.JewelTheme
+import org.jetbrains.jewel.foundation.theme.ThemeColorPalette
+import org.jetbrains.jewel.foundation.theme.ThemeDefinition
+import org.jetbrains.jewel.foundation.theme.ThemeIconData
+import org.jetbrains.jewel.markdown.rendering.InlinesStyling
+import org.jetbrains.jewel.markdown.rendering.MarkdownStyling
+import org.jetbrains.jewel.markdown.rendering.MarkdownStyling.List.Ordered.NumberFormatStyles.NumberFormatStyle
+import org.jetbrains.jewel.markdown.rendering.MarkdownStyling.List.Unordered.BulletCharStyles
+import org.jetbrains.jewel.ui.component.styling.DividerMetrics
+import org.jetbrains.jewel.ui.component.styling.DividerStyle
+import org.jetbrains.jewel.ui.component.styling.LocalDividerStyle
+import org.jetbrains.jewel.ui.component.styling.LocalScrollbarStyle
+import org.jetbrains.jewel.ui.component.styling.ScrollbarColors
+import org.jetbrains.jewel.ui.component.styling.ScrollbarMetrics
+import org.jetbrains.jewel.ui.component.styling.ScrollbarStyle
+import org.jetbrains.jewel.ui.component.styling.ScrollbarVisibility
+import org.jetbrains.jewel.ui.component.styling.TrackClickBehavior
+
+@Composable
+fun MarkdownTestTheme(content: @Composable () -> Unit) {
+ CompositionLocalProvider(
+ LocalCodeHighlighter provides NoOpCodeHighlighter,
+ LocalDividerStyle provides createMarkdownTestDividerStyle(),
+ LocalScrollbarStyle provides createMarkdownTestScrollbarStyle(),
+ LocalDensity provides Density(1f),
+ ) {
+ JewelTheme(createMarkdownTestThemeDefinition()) { content() }
+ }
+}
+
+fun createMarkdownTestDividerStyle() =
+ DividerStyle(color = Color.Black, metrics = DividerMetrics(thickness = 1.dp, startIndent = 0.dp))
+
+fun createMarkdownTestScrollbarStyle() =
+ ScrollbarStyle(
+ colors =
+ ScrollbarColors(
+ thumbBackground = Color.Black,
+ thumbBorderActive = Color.Black,
+ thumbBackgroundActive = Color.Black,
+ thumbOpaqueBackground = Color.Black,
+ thumbOpaqueBackgroundHovered = Color.Black,
+ thumbBorder = Color.Black,
+ thumbOpaqueBorder = Color.Black,
+ thumbOpaqueBorderHovered = Color.Black,
+ trackBackground = Color.Black,
+ trackBackgroundExpanded = Color.Black,
+ trackOpaqueBackground = Color.Black,
+ trackOpaqueBackgroundHovered = Color.Black,
+ ),
+ metrics = ScrollbarMetrics(thumbCornerSize = CornerSize(1.dp), minThumbLength = 1.dp),
+ trackClickBehavior = TrackClickBehavior.NextPage,
+ scrollbarVisibility =
+ ScrollbarVisibility.AlwaysVisible(
+ trackThickness = 1.dp,
+ trackPadding = PaddingValues(1.dp),
+ trackPaddingWithBorder = PaddingValues(1.dp),
+ thumbColorAnimationDuration = 500.milliseconds,
+ trackColorAnimationDuration = 500.milliseconds,
+ scrollbarBackgroundColorLight = Color.White,
+ scrollbarBackgroundColorDark = Color.White,
+ ),
+ )
+
+fun createMarkdownTestThemeDefinition(): ThemeDefinition =
+ ThemeDefinition(
+ name = "Test",
+ isDark = false,
+ globalColors =
+ GlobalColors(
+ borders = BorderColors(normal = Color.Black, focused = Color.Black, disabled = Color.Black),
+ outlines =
+ OutlineColors(
+ focused = Color.Black,
+ focusedWarning = Color.Black,
+ focusedError = Color.Black,
+ warning = Color.Black,
+ error = Color.Black,
+ ),
+ text =
+ TextColors(
+ normal = Color.Black,
+ selected = Color.Black,
+ disabled = Color.Black,
+ disabledSelected = Color.Black,
+ info = Color.Black,
+ error = Color.Black,
+ warning = Color.Black,
+ ),
+ panelBackground = Color.White,
+ toolwindowBackground = Color.White,
+ ),
+ globalMetrics = GlobalMetrics(outlineWidth = 10.dp, rowHeight = 24.dp),
+ defaultTextStyle = TextStyle.Default,
+ editorTextStyle = TextStyle.Default,
+ consoleTextStyle = TextStyle.Default,
+ contentColor = Color.Black,
+ colorPalette = ThemeColorPalette.Empty,
+ iconData = ThemeIconData.Empty,
+ disabledAppearanceValues = DisabledAppearanceValues(brightness = 33, contrast = -35, alpha = 100),
+ )
+
+fun createMarkdownTestStyling(codeEditorTextStyle: TextStyle = TextStyle.Default): MarkdownStyling {
+ val mockSpanStyle = SpanStyle(Color.Black)
+ val inlinesStyling =
+ InlinesStyling(
+ textStyle = TextStyle.Default,
+ inlineCode = mockSpanStyle,
+ link = mockSpanStyle,
+ linkDisabled = mockSpanStyle,
+ linkFocused = mockSpanStyle,
+ linkHovered = mockSpanStyle,
+ linkPressed = mockSpanStyle,
+ linkVisited = mockSpanStyle,
+ emphasis = mockSpanStyle,
+ strongEmphasis = mockSpanStyle,
+ inlineHtml = mockSpanStyle,
+ )
+ return MarkdownStyling(
+ blockVerticalSpacing = 8.dp,
+ paragraph = MarkdownStyling.Paragraph(inlinesStyling),
+ heading =
+ MarkdownStyling.Heading(
+ h1 = MarkdownStyling.Heading.H1(inlinesStyling, 1.dp, Color.Black, 2.dp, PaddingValues(4.dp)),
+ h2 = MarkdownStyling.Heading.H2(inlinesStyling, 1.dp, Color.Black, 2.dp, PaddingValues(4.dp)),
+ h3 = MarkdownStyling.Heading.H3(inlinesStyling, 1.dp, Color.Black, 2.dp, PaddingValues(4.dp)),
+ h4 = MarkdownStyling.Heading.H4(inlinesStyling, 1.dp, Color.Black, 2.dp, PaddingValues(4.dp)),
+ h5 = MarkdownStyling.Heading.H5(inlinesStyling, 1.dp, Color.Black, 2.dp, PaddingValues(4.dp)),
+ h6 = MarkdownStyling.Heading.H6(inlinesStyling, 1.dp, Color.Black, 2.dp, PaddingValues(4.dp)),
+ ),
+ blockQuote =
+ MarkdownStyling.BlockQuote(
+ padding = PaddingValues(4.dp),
+ lineWidth = 2.dp,
+ lineColor = Color.Gray,
+ pathEffect = null,
+ strokeCap = StrokeCap.Square,
+ textColor = Color.Black,
+ ),
+ code =
+ MarkdownStyling.Code(
+ indented =
+ MarkdownStyling.Code.Indented(
+ editorTextStyle = codeEditorTextStyle,
+ padding = PaddingValues(4.dp),
+ shape = RectangleShape,
+ background = Color.LightGray,
+ borderWidth = 0.dp,
+ borderColor = Color.DarkGray,
+ fillWidth = true,
+ scrollsHorizontally = true,
+ ),
+ fenced =
+ MarkdownStyling.Code.Fenced(
+ editorTextStyle = codeEditorTextStyle,
+ padding = PaddingValues(4.dp),
+ shape = RectangleShape,
+ background = Color.LightGray,
+ borderWidth = 0.dp,
+ borderColor = Color.DarkGray,
+ fillWidth = true,
+ scrollsHorizontally = true,
+ infoTextStyle = TextStyle.Default,
+ infoPadding = PaddingValues(2.dp),
+ infoPosition = MarkdownStyling.Code.Fenced.InfoPosition.TopStart,
+ ),
+ ),
+ list =
+ MarkdownStyling.List(
+ ordered =
+ MarkdownStyling.List.Ordered(
+ numberStyle = TextStyle.Default,
+ numberContentGap = 1.dp,
+ numberMinWidth = 2.dp,
+ numberTextAlign = TextAlign.Start,
+ itemVerticalSpacing = 4.dp,
+ itemVerticalSpacingTight = 2.dp,
+ padding = PaddingValues(4.dp),
+ numberFormatStyles =
+ MarkdownStyling.List.Ordered.NumberFormatStyles(firstLevel = NumberFormatStyle.Decimal),
+ ),
+ unordered =
+ MarkdownStyling.List.Unordered(
+ bullet = '.',
+ bulletStyle = TextStyle.Default,
+ bulletContentGap = 1.dp,
+ itemVerticalSpacing = 4.dp,
+ itemVerticalSpacingTight = 2.dp,
+ padding = PaddingValues(4.dp),
+ markerMinWidth = 16.dp,
+ bulletCharStyles = BulletCharStyles(),
+ ),
+ ),
+ image =
+ MarkdownStyling.Image(
+ alignment = Alignment.Center,
+ contentScale = ContentScale.Crop,
+ padding = PaddingValues(8.dp),
+ shape = RectangleShape,
+ background = Color.Transparent,
+ borderWidth = 1.dp,
+ borderColor = Color.LightGray,
+ ),
+ thematicBreak =
+ MarkdownStyling.ThematicBreak(padding = PaddingValues(4.dp), lineWidth = 2.dp, lineColor = Color.DarkGray),
+ htmlBlock =
+ MarkdownStyling.HtmlBlock(
+ textStyle = TextStyle.Default,
+ padding = PaddingValues(4.dp),
+ shape = RectangleShape,
+ background = Color.White,
+ borderWidth = 1.dp,
+ borderColor = Color.Gray,
+ fillWidth = true,
+ ),
+ )
+}
diff --git a/platform/jewel/settings.gradle.kts b/platform/jewel/settings.gradle.kts
index 2931a57bff13f..53fb03bcc128b 100644
--- a/platform/jewel/settings.gradle.kts
+++ b/platform/jewel/settings.gradle.kts
@@ -47,6 +47,7 @@ include(
":markdown:extensions:images",
":markdown:int-ui-standalone-styling",
":markdown:ide-laf-bridge-styling",
+ ":markdown:testing",
":samples:showcase",
":samples:standalone",
":ui",