Browse Source

Introduce basic support for SVG (#1289)

pull/1296/head
Shagen Ogandzhanian 3 years ago committed by GitHub
parent
commit
095379d513
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/ComposePlugin.kt
  2. 1
      web/settings.gradle.kts
  3. 49
      web/svg/build.gradle.kts
  4. 9
      web/svg/src/jsMain/kotlin/org/jetbrains/compose/web/ExperimentalComposeWebSvgApi.kt
  5. 729
      web/svg/src/jsMain/kotlin/org/jetbrains/compose/web/svg/svg.kt
  6. 453
      web/svg/src/jsTest/kotlin/svg/SvgTests.kt
  7. 1
      web/test-utils/src/jsMain/kotlin/org/jetbrains/compose/web/testutils/TestUtils.kt

4
gradle-plugins/compose/src/main/kotlin/org/jetbrains/compose/ComposePlugin.kt

@ -197,6 +197,10 @@ class ComposePlugin : Plugin<Project> {
composeDependency("org.jetbrains.compose.web:web-core")
}
val svg by lazy {
composeDependency("org.jetbrains.compose.web:web-svg")
}
val widgets by lazy {
composeDependency("org.jetbrains.compose.web:web-widgets")
}

1
web/settings.gradle.kts

@ -35,6 +35,7 @@ fun module(name: String, path: String) {
module(":web-core", "core")
module(":web-svg", "svg")
module(":web-widgets", "widgets")
module(":web-integration-core", "integration-core")
module(":web-integration-widgets", "integration-widgets")

49
web/svg/build.gradle.kts

@ -0,0 +1,49 @@
plugins {
kotlin("multiplatform")
id("org.jetbrains.compose")
}
kotlin {
js(IR) {
browser() {
testTask {
testLogging.showStandardStreams = true
useKarma {
useChromeHeadless()
}
}
}
binaries.executable()
}
sourceSets {
val commonMain by getting {
dependencies {
implementation(compose.runtime)
implementation(kotlin("stdlib-common"))
}
}
val jsMain by getting {
dependencies {
implementation(project(":internal-web-core-runtime"))
implementation(kotlin("stdlib-js"))
implementation(project(":web-core"))
}
}
val jsTest by getting {
dependencies {
implementation(project(":test-utils"))
implementation(kotlin("test-js"))
}
}
all {
languageSettings {
useExperimentalAnnotation("org.jetbrains.compose.web.testutils.ComposeWebExperimentalTestsApi")
}
}
}
}

9
web/svg/src/jsMain/kotlin/org/jetbrains/compose/web/ExperimentalComposeWebSvgApi.kt

@ -0,0 +1,9 @@
/*
* Copyright 2020-2021 JetBrains s.r.o. and respective authors and developers.
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file.
*/
package org.jetbrains.compose.web
@RequiresOptIn("This API is experimental and is likely to change in the future.")
annotation class ExperimentalComposeWebSvgApi

729
web/svg/src/jsMain/kotlin/org/jetbrains/compose/web/svg/svg.kt

@ -0,0 +1,729 @@
/*
* Copyright 2020-2021 JetBrains s.r.o. and respective authors and developers.
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file.
*/
package org.jetbrains.compose.web.svg
import androidx.compose.runtime.Composable
import kotlinx.browser.document
import org.jetbrains.compose.web.ExperimentalComposeWebSvgApi
import org.jetbrains.compose.web.css.CSSLengthOrPercentageValue
import org.jetbrains.compose.web.dom.*
import org.w3c.css.masking.SVGClipPathElement
import org.w3c.css.masking.SVGMaskElement
import org.w3c.dom.Element
import org.w3c.dom.svg.*
private open class ElementBuilderNS<TElement : Element>(private val tagName: String, private val namespace: String) :
ElementBuilder<TElement> {
private val el: Element by lazy { document.createElementNS(namespace, tagName) }
override fun create(): TElement = el.cloneNode().unsafeCast<TElement>()
}
const val SVG_NS = "http://www.w3.org/2000/svg"
private val A = ElementBuilderNS<SVGAElement>("a", SVG_NS)
private val Animate = ElementBuilderNS<SVGElement>("animate", SVG_NS)
private val AnimateMotion = ElementBuilderNS<SVGElement>("animateMotion", SVG_NS)
private val AnimateTransform = ElementBuilderNS<SVGElement>("animateTransform", SVG_NS)
private val Circle = ElementBuilderNS<SVGCircleElement>("circle", SVG_NS)
private val ClipPath = ElementBuilderNS<SVGClipPathElement>("clipPath", SVG_NS)
private val Defs = ElementBuilderNS<SVGDefsElement>("defs", SVG_NS)
private val Desc = ElementBuilderNS<SVGDescElement>("desc", SVG_NS)
private val Ellipse = ElementBuilderNS<SVGEllipseElement>("ellipse", SVG_NS)
private val Filter = ElementBuilderNS<SVGElement>("filter", SVG_NS)
private val G = ElementBuilderNS<SVGElement>("g", SVG_NS)
private val Image = ElementBuilderNS<SVGImageElement>("image", SVG_NS)
private val Line = ElementBuilderNS<SVGLineElement>("line", SVG_NS)
private val LinearGradient = ElementBuilderNS<SVGLinearGradientElement>("linearGradient", SVG_NS)
private val Marker = ElementBuilderNS<SVGMarkerElement>("marker", SVG_NS)
private val Mask = ElementBuilderNS<SVGMaskElement>("mask", SVG_NS)
private val Mpath = ElementBuilderNS<SVGElement>("mpath", SVG_NS)
private val Path = ElementBuilderNS<SVGPathElement>("path", SVG_NS)
private val Pattern = ElementBuilderNS<SVGPatternElement>("pattern", SVG_NS)
private val Polygon = ElementBuilderNS<SVGPolygonElement>("polygon", SVG_NS)
private val Polyline = ElementBuilderNS<SVGPolylineElement>("polyline", SVG_NS)
private val RadialGradient = ElementBuilderNS<SVGRadialGradientElement>("radialGradient", SVG_NS)
private val Rect = ElementBuilderNS<SVGRectElement>("rect", SVG_NS)
private val Set = ElementBuilderNS<SVGElement>("set", SVG_NS)
private val Stop = ElementBuilderNS<SVGStopElement>("stop", SVG_NS)
private val Svg = ElementBuilderNS<SVGElement>("svg", SVG_NS)
private val Switch = ElementBuilderNS<SVGSwitchElement>("switch", SVG_NS)
private val Symbol = ElementBuilderNS<SVGSymbolElement>("symbol", SVG_NS)
private val Text = ElementBuilderNS<SVGTextElement>("text", SVG_NS)
private val TextPath = ElementBuilderNS<SVGTextPathElement>("textPath", SVG_NS)
private val Title = ElementBuilderNS<SVGTitleElement>("title", SVG_NS)
private val Tspan = ElementBuilderNS<SVGTSpanElement>("tspan", SVG_NS)
private val Use = ElementBuilderNS<SVGUseElement>("use", SVG_NS)
private val View = ElementBuilderNS<SVGViewElement>("view", SVG_NS)
@Composable
@ExperimentalComposeWebSvgApi
fun Svg(
viewBox: String? = null,
attrs: AttrBuilderContext<SVGElement>? = null,
content: ContentBuilder<SVGElement>? = null
) {
TagElement(
elementBuilder = Svg,
applyAttrs = {
viewBox?.let { attr("viewBox", it) }
attrs?.invoke(this)
},
content = content
)
}
@Composable
@ExperimentalComposeWebSvgApi
fun ElementScope<SVGElement>.SvgA(
href: String,
attrs: AttrBuilderContext<SVGAElement>? = null,
content: ContentBuilder<SVGAElement>? = null
) {
TagElement(
elementBuilder = A,
applyAttrs = {
attr("href", href)
attrs?.invoke(this)
},
content = content
)
}
@Composable
@ExperimentalComposeWebSvgApi
fun ElementScope<SVGElement>.Circle(
cx: CSSLengthOrPercentageValue,
cy: CSSLengthOrPercentageValue,
r: CSSLengthOrPercentageValue,
attrs: AttrBuilderContext<SVGCircleElement>? = null,
content: ContentBuilder<SVGCircleElement>? = null
) {
TagElement(
elementBuilder = Circle,
applyAttrs = {
attr("cx", cx.toString())
attr("cy", cy.toString())
attr("r", r.toString())
attrs?.invoke(this)
},
content = content
)
}
@Composable
@ExperimentalComposeWebSvgApi
fun ElementScope<SVGElement>.Circle(
cx: Number,
cy: Number,
r: Number,
attrs: AttrBuilderContext<SVGCircleElement>? = null,
content: ContentBuilder<SVGCircleElement>? = null
) {
TagElement(
elementBuilder = Circle,
applyAttrs = {
attr("cx", cx.toString())
attr("cy", cy.toString())
attr("r", r.toString())
attrs?.invoke(this)
},
content = content
)
}
@Composable
@ExperimentalComposeWebSvgApi
fun ElementScope<SVGElement>.SvgText(
text: String,
x: Number = 0,
y: Number = 0,
attrs: AttrBuilderContext<SVGTextElement>? = null,
) {
TagElement(
elementBuilder = Text,
applyAttrs = {
attr("x", x.toString())
attr("y", y.toString())
attrs?.invoke(this)
},
content = {
Text(text)
}
)
}
@Composable
@ExperimentalComposeWebSvgApi
fun ElementScope<SVGElement>.View(
id: String,
viewBox: String,
attrs: AttrBuilderContext<SVGViewElement>? = null,
) {
TagElement(
elementBuilder = View,
applyAttrs = {
attr("id", id)
attr("viewBox", viewBox)
attrs?.invoke(this)
},
content = null
)
}
@Composable
@ExperimentalComposeWebSvgApi
fun ElementScope<SVGElement>.Rect(
x: Number,
y: Number,
width: Number,
height: Number,
attrs: AttrBuilderContext<SVGRectElement>? = null,
content: ContentBuilder<SVGRectElement>? = null
) {
TagElement(
elementBuilder = Rect,
applyAttrs = {
attr("x", x.toString())
attr("y", y.toString())
attr("width", width.toString())
attr("height", height.toString())
attrs?.invoke(this)
},
content = content
)
}
@Composable
@ExperimentalComposeWebSvgApi
fun ElementScope<SVGElement>.Rect(
x: CSSLengthOrPercentageValue,
y: CSSLengthOrPercentageValue,
width: CSSLengthOrPercentageValue,
height: CSSLengthOrPercentageValue,
attrs: AttrBuilderContext<SVGRectElement>? = null,
content: ContentBuilder<SVGRectElement>? = null
) {
TagElement(
elementBuilder = Rect,
applyAttrs = {
attr("x", x.toString())
attr("y", y.toString())
attr("width", width.toString())
attr("height", height.toString())
attrs?.invoke(this)
},
content = content
)
}
@Composable
@ExperimentalComposeWebSvgApi
fun ElementScope<SVGElement>.Ellipse(
cx: CSSLengthOrPercentageValue,
cy: CSSLengthOrPercentageValue,
rx: CSSLengthOrPercentageValue,
ry: CSSLengthOrPercentageValue,
attrs: AttrBuilderContext<SVGEllipseElement>? = null,
content: ContentBuilder<SVGEllipseElement>? = null
) {
TagElement(
elementBuilder = Ellipse,
applyAttrs = {
attr("cx", cx.toString())
attr("cy", cy.toString())
attr("rx", rx.toString())
attr("ry", ry.toString())
attrs?.invoke(this)
},
content = content
)
}
@Composable
@ExperimentalComposeWebSvgApi
fun ElementScope<SVGElement>.Ellipse(
cx: Number,
cy: Number,
rx: Number,
ry: Number,
attrs: AttrBuilderContext<SVGEllipseElement>? = null,
content: ContentBuilder<SVGEllipseElement>? = null
) {
TagElement(
elementBuilder = Ellipse,
applyAttrs = {
attr("cx", cx.toString())
attr("cy", cy.toString())
attr("rx", rx.toString())
attr("ry", ry.toString())
attrs?.invoke(this)
},
content = content
)
}
@Composable
@ExperimentalComposeWebSvgApi
fun ElementScope<SVGElement>.Symbol(
id: String? = null,
attrs: AttrBuilderContext<SVGSymbolElement>? = null,
content: ContentBuilder<SVGSymbolElement>? = null
) {
TagElement(
elementBuilder = Symbol,
applyAttrs = {
id?.let { attr("id", it) }
attrs?.invoke(this)
},
content = content
)
}
@Composable
@ExperimentalComposeWebSvgApi
fun ElementScope<SVGElement>.Use(
href: String,
attrs: AttrBuilderContext<SVGUseElement>? = null,
content: ContentBuilder<SVGUseElement>? = null
) {
TagElement(
elementBuilder = Use,
applyAttrs = {
attr("href", href)
attrs?.invoke(this)
},
content = content
)
}
@Composable
@ExperimentalComposeWebSvgApi
fun ElementScope<SVGElement>.Line(
x1: CSSLengthOrPercentageValue,
y1: CSSLengthOrPercentageValue,
x2: CSSLengthOrPercentageValue,
y2: CSSLengthOrPercentageValue,
attrs: AttrBuilderContext<SVGLineElement>? = null,
content: ContentBuilder<SVGLineElement>? = null
) {
TagElement(
elementBuilder = Line,
applyAttrs = {
attr("x1", x1.toString())
attr("y1", y1.toString())
attr("x2", x2.toString())
attr("y2", y2.toString())
attrs?.invoke(this)
},
content = content
)
}
@Composable
@ExperimentalComposeWebSvgApi
fun ElementScope<SVGElement>.Line(
x1: Number,
y1: Number,
x2: Number,
y2: Number,
attrs: AttrBuilderContext<SVGLineElement>? = null,
content: ContentBuilder<SVGLineElement>? = null
) {
TagElement(
elementBuilder = Line,
applyAttrs = {
attr("x1", x1.toString())
attr("y1", y1.toString())
attr("x2", x2.toString())
attr("y2", y2.toString())
attrs?.invoke(this)
},
content = content
)
}
@Composable
@ExperimentalComposeWebSvgApi
fun ElementScope<SVGElement>.ClipPath(
id: String,
attrs: AttrBuilderContext<SVGClipPathElement>? = null,
content: ContentBuilder<SVGClipPathElement>? = null
) {
TagElement(
elementBuilder = ClipPath,
applyAttrs = {
attr("id", id)
attrs?.invoke(this)
},
content = content
)
}
@Composable
@ExperimentalComposeWebSvgApi
fun ElementScope<SVGElement>.Path(
d: String,
attrs: AttrBuilderContext<SVGPathElement>? = null,
content: ContentBuilder<SVGPathElement>? = null
) {
TagElement(
elementBuilder = Path,
applyAttrs = {
attr("d", d)
attrs?.invoke(this)
},
content = content
)
}
@Composable
@ExperimentalComposeWebSvgApi
fun ElementScope<SVGElement>.G(
attrs: AttrBuilderContext<SVGElement>? = null,
content: ContentBuilder<SVGElement>? = null
) {
TagElement(
elementBuilder = G,
applyAttrs = attrs,
content = content
)
}
@Composable
@ExperimentalComposeWebSvgApi
fun ElementScope<SVGElement>.Image(
href: String,
attrs: AttrBuilderContext<SVGImageElement>? = null,
content: ContentBuilder<SVGImageElement>? = null
) {
TagElement(
elementBuilder = Image,
applyAttrs = {
attr("href", href)
attrs?.invoke(this)
},
content = content
)
}
@Composable
@ExperimentalComposeWebSvgApi
fun ElementScope<SVGElement>.Mask(
id: String? = null,
attrs: AttrBuilderContext<SVGMaskElement>? = null,
content: ContentBuilder<SVGMaskElement>? = null
) {
TagElement(
elementBuilder = Mask,
applyAttrs = {
id?.let { attr("id", it) }
attrs?.invoke(this)
},
content = content
)
}
@Composable
@ExperimentalComposeWebSvgApi
fun ElementScope<SVGElement>.Defs(
attrs: AttrBuilderContext<SVGDefsElement>? = null,
content: ContentBuilder<SVGDefsElement>? = null
) {
TagElement(
elementBuilder = Defs,
applyAttrs = attrs,
content = content
)
}
@Composable
@ExperimentalComposeWebSvgApi
fun ElementScope<SVGElement>.Pattern(
id: String,
attrs: AttrBuilderContext<SVGPatternElement>? = null,
content: ContentBuilder<SVGPatternElement>? = null
) {
TagElement(
elementBuilder = Pattern,
applyAttrs = {
attr("id", id)
attrs?.invoke(this)
},
content = content
)
}
@Composable
@ExperimentalComposeWebSvgApi
fun ElementScope<SVGElement>.Polygon(
vararg points: Number,
attrs: AttrBuilderContext<SVGPolygonElement>? = null,
content: ContentBuilder<SVGPolygonElement>? = null
) {
TagElement(
elementBuilder = Polygon,
applyAttrs = {
attr("points", points.toList().chunked(2).joinToString(" ") { it.joinToString(",") })
attrs?.invoke(this)
},
content = content
)
}
@Composable
@ExperimentalComposeWebSvgApi
fun ElementScope<SVGElement>.Polyline(
vararg points: Number,
attrs: AttrBuilderContext<SVGPolylineElement>? = null,
content: ContentBuilder<SVGPolylineElement>? = null
) {
TagElement(
elementBuilder = Polyline,
applyAttrs = {
attr("points", points.toList().chunked(2).joinToString(" ") { it.joinToString(",") })
attrs?.invoke(this)
},
content = content
)
}
@Composable
@ExperimentalComposeWebSvgApi
fun ElementScope<SVGElement>.TextPath(
href: String,
text: String,
attrs: AttrBuilderContext<SVGTextPathElement>? = null,
) {
TagElement(
elementBuilder = TextPath,
applyAttrs = {
attr("href", href)
attrs?.invoke(this)
},
content = {
Text(text)
}
)
}
@Composable
@ExperimentalComposeWebSvgApi
fun ElementScope<SVGElement>.Animate(
attrs: AttrBuilderContext<SVGElement>? = null,
content: ContentBuilder<SVGElement>? = null
) {
TagElement(
elementBuilder = Animate,
applyAttrs = attrs,
content = content
)
}
@Composable
@ExperimentalComposeWebSvgApi
fun ElementScope<SVGElement>.AnimateMotion(
attrs: AttrBuilderContext<SVGElement>? = null,
content: ContentBuilder<SVGElement>? = null
) {
TagElement(
elementBuilder = AnimateMotion,
applyAttrs = attrs,
content = content
)
}
@Composable
@ExperimentalComposeWebSvgApi
fun ElementScope<SVGElement>.AnimateTransform(
attrs: AttrBuilderContext<SVGElement>? = null,
content: ContentBuilder<SVGElement>? = null
) {
TagElement(
elementBuilder = AnimateTransform,
applyAttrs = attrs,
content = content
)
}
@Composable
@ExperimentalComposeWebSvgApi
fun ElementScope<SVGElement>.LinearGradient(
id: String? = null,
attrs: AttrBuilderContext<SVGLinearGradientElement>? = null,
content: ContentBuilder<SVGLinearGradientElement>? = null
) {
TagElement(
elementBuilder = LinearGradient,
applyAttrs = {
id?.let { attr("id", it) }
attrs?.invoke(this)
},
content = content
)
}
@Composable
@ExperimentalComposeWebSvgApi
fun ElementScope<SVGElement>.RadialGradient(
id: String? = null,
attrs: AttrBuilderContext<SVGRadialGradientElement>? = null,
content: ContentBuilder<SVGRadialGradientElement>? = null
) {
TagElement(
elementBuilder = RadialGradient,
applyAttrs = {
id?.let { attr("id", it) }
attrs?.invoke(this)
},
content = content
)
}
@Composable
@ExperimentalComposeWebSvgApi
fun ElementScope<SVGElement>.Stop(
attrs: AttrBuilderContext<SVGStopElement>? = null,
content: ContentBuilder<SVGStopElement>? = null
) {
TagElement(
elementBuilder = Stop,
applyAttrs = attrs,
content = content
)
}
@Composable
@ExperimentalComposeWebSvgApi
fun ElementScope<SVGElement>.Switch(
attrs: AttrBuilderContext<SVGSwitchElement>? = null,
content: ContentBuilder<SVGSwitchElement>? = null
) {
TagElement(
elementBuilder = Switch,
applyAttrs = attrs,
content = content
)
}
@Composable
@ExperimentalComposeWebSvgApi
fun ElementScope<SVGElement>.Title(
text: String,
attrs: AttrBuilderContext<SVGTitleElement>? = null,
) {
TagElement(
elementBuilder = Title,
applyAttrs = attrs,
content = {
Text(text)
}
)
}
@Composable
@ExperimentalComposeWebSvgApi
fun ElementScope<SVGElement>.Tspan(
attrs: AttrBuilderContext<SVGTSpanElement>? = null,
content: ContentBuilder<SVGTSpanElement>? = null
) {
TagElement(
elementBuilder = Tspan,
applyAttrs = attrs,
content = content
)
}
@Composable
@ExperimentalComposeWebSvgApi
fun ElementScope<SVGElement>.Desc(
content: String,
attrs: AttrBuilderContext<SVGDescElement>? = null,
) {
TagElement(
elementBuilder = Desc,
applyAttrs = attrs,
content = {
Text(content)
}
)
}
@Composable
@ExperimentalComposeWebSvgApi
fun ElementScope<SVGElement>.Marker(
attrs: AttrBuilderContext<SVGMarkerElement>? = null,
content: ContentBuilder<SVGMarkerElement>? = null
) {
TagElement(
elementBuilder = Marker,
applyAttrs = attrs,
content = content
)
}
@Composable
@ExperimentalComposeWebSvgApi
fun ElementScope<SVGElement>.Mpath(
attrs: AttrBuilderContext<SVGElement>? = null,
content: ContentBuilder<SVGElement>? = null
) {
TagElement(
elementBuilder = Mpath,
applyAttrs = attrs,
content = content
)
}
@Composable
@ExperimentalComposeWebSvgApi
fun ElementScope<SVGElement>.Filter(
attrs: AttrBuilderContext<SVGElement>? = null,
content: ContentBuilder<SVGElement>? = null
) {
TagElement(
elementBuilder = Filter,
applyAttrs = attrs,
content = content
)
}
@Composable
@ExperimentalComposeWebSvgApi
fun ElementScope<SVGElement>.Set(
attributeName: String,
to: String,
attrs: AttrBuilderContext<SVGElement>? = null,
content: ContentBuilder<SVGElement>? = null
) {
TagElement(
elementBuilder = Set,
applyAttrs = {
attr("attributeName", attributeName)
attr("to", to)
attrs?.invoke(this)
},
content = content
)
}
@Composable
@ExperimentalComposeWebSvgApi
fun <T : SVGElement> SvgElement(
name: String,
attrs: AttrBuilderContext<T>? = null,
content: ContentBuilder<T>? = null
) {
TagElement(
elementBuilder = ElementBuilderNS(name, SVG_NS),
applyAttrs = attrs,
content = content
)
}

453
web/svg/src/jsTest/kotlin/svg/SvgTests.kt

@ -0,0 +1,453 @@
/*
* Copyright 2020-2021 JetBrains s.r.o. and respective authors and developers.
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file.
*/
package org.jetbrains.compose.web.core.tests.svg
import org.jetbrains.compose.web.ExperimentalComposeWebSvgApi
import org.jetbrains.compose.web.css.opacity
import org.jetbrains.compose.web.css.percent
import org.jetbrains.compose.web.css.px
import org.jetbrains.compose.web.svg.*
import org.jetbrains.compose.web.testutils.*
import org.w3c.dom.svg.SVGCircleElement
import kotlin.test.Test
import kotlin.test.assertEquals
@ExperimentalComposeWebSvgApi
class SvgTests {
@Test
fun nodeNames() = runTest {
composition {
Svg {
Animate()
AnimateMotion()
AnimateTransform()
Defs()
Filter()
G()
Marker()
Mpath()
Switch()
Tspan()
}
}
assertEquals(
"<svg><animate></animate><animateMotion></animateMotion><animateTransform></animateTransform><defs></defs><filter></filter><g></g><marker></marker><mpath></mpath><switch></switch><tspan></tspan></svg>",
nextChild<SVGCircleElement>().outerHTML
)
}
@Test
fun clipPathTest() = runTest {
composition {
Svg {
ClipPath("myClip") {
Circle(40.px, 35.px, 36.px)
}
}
}
assertEquals(
"<svg><clipPath id=\"myClip\"><circle cx=\"40px\" cy=\"35px\" r=\"36px\"></circle></clipPath></svg>",
nextChild<SVGCircleElement>().outerHTML
)
}
@Test
fun maskTest() = runTest {
composition {
Svg {
Mask("myMask")
}
}
assertEquals("<svg><mask id=\"myMask\"></mask></svg>", nextChild<SVGCircleElement>().outerHTML)
}
@Test
fun svgATest() = runTest {
composition {
Svg {
SvgA("/docs/Web/SVG/Element/circle", {
attr("target", "_blank")
})
}
}
assertEquals(
"<svg><a href=\"/docs/Web/SVG/Element/circle\" target=\"_blank\"></a></svg>",
nextChild<SVGCircleElement>().outerHTML
)
}
@Test
fun descTest() = runTest {
composition {
Svg {
Desc("some description", { attr("id", "myDesc") })
}
}
assertEquals("<svg><desc id=\"myDesc\">some description</desc></svg>", nextChild<SVGCircleElement>().outerHTML)
}
@Test
fun setTest() = runTest {
composition {
Svg {
Rect(0, 0, 10, 10, { attr("id", "rect") }) {
Set(attributeName = "class", to = "round", { attr("begin", "me.click"); attr("dur", "2s") })
}
}
}
assertEquals(
"<svg><rect x=\"0\" y=\"0\" width=\"10\" height=\"10\" id=\"rect\"><set attributeName=\"class\" to=\"round\" begin=\"me.click\" dur=\"2s\"></set></rect></svg>",
nextChild<SVGCircleElement>().outerHTML
)
}
@Test
fun titleTest() = runTest {
composition {
Svg {
Rect(10, 20, 30, 30) {
Title("some title")
}
}
}
assertEquals(
"<svg><rect x=\"10\" y=\"20\" width=\"30\" height=\"30\"><title>some title</title></rect></svg>",
nextChild<SVGCircleElement>().outerHTML
)
}
@Test
fun svgTextTest() = runTest {
composition {
Svg {
SvgText("some text", 20, 30, {
classes("small")
})
}
}
assertEquals(
"<svg><text x=\"20\" y=\"30\" class=\"small\">some text</text></svg>",
nextChild<SVGCircleElement>().outerHTML
)
}
@Test
fun textPathTest() = runTest {
composition {
Svg {
TextPath("#someHref", "Some text")
}
}
assertEquals(
"<svg><textPath href=\"#someHref\">Some text</textPath></svg>",
nextChild<SVGCircleElement>().outerHTML
)
}
@Test
fun ellipseTest() = runTest {
composition {
Svg {
Ellipse(50, 60, 70, 20, {
attr("color", "yellow")
})
Ellipse(50.px, 60.px, 70.percent, 20.px, {
attr("color", "red")
})
}
}
assertEquals(
"<svg><ellipse cx=\"50\" cy=\"60\" rx=\"70\" ry=\"20\" color=\"yellow\"></ellipse><ellipse cx=\"50px\" cy=\"60px\" rx=\"70%\" ry=\"20px\" color=\"red\"></ellipse></svg>",
nextChild<SVGCircleElement>().outerHTML
)
}
@Test
fun circleTest() = runTest {
composition {
Svg {
Circle(50, 60, 70, {
attr("color", "red")
})
Circle(50.px, 60.px, 70.percent, {
attr("color", "red")
})
}
}
assertEquals(
"<svg><circle cx=\"50\" cy=\"60\" r=\"70\" color=\"red\"></circle><circle cx=\"50px\" cy=\"60px\" r=\"70%\" color=\"red\"></circle></svg>",
nextChild<SVGCircleElement>().outerHTML
)
}
@Test
fun rectTest() = runTest {
composition {
Svg {
Rect(0, 20, 100, 200, {
attr("color", "red")
})
Rect(0.px, 20.px, 100.px, 200.px, {
attr("color", "red")
})
}
}
assertEquals(
"<svg><rect x=\"0\" y=\"20\" width=\"100\" height=\"200\" color=\"red\"></rect><rect x=\"0px\" y=\"20px\" width=\"100px\" height=\"200px\" color=\"red\"></rect></svg>",
nextChild<SVGCircleElement>().outerHTML
)
}
@Test
fun imageTest() = runTest {
composition {
Svg {
Image("/image.png", {
attr("preserveAspectRatio", "xMidYMid meet")
})
}
}
assertEquals(
"<svg><image href=\"/image.png\" preserveAspectRatio=\"xMidYMid meet\"></image></svg>",
nextChild<SVGCircleElement>().outerHTML
)
}
@Test
fun lineTest() = runTest {
composition {
Svg {
Line(0, 80, 100, 20, {
attr("stroke", "red")
})
Line(0.px, 80.px, 100.px, 20.px, {
attr("stroke", "black")
})
}
}
assertEquals(
"<svg><line x1=\"0\" y1=\"80\" x2=\"100\" y2=\"20\" stroke=\"red\"></line><line x1=\"0px\" y1=\"80px\" x2=\"100px\" y2=\"20px\" stroke=\"black\"></line></svg>",
nextChild<SVGCircleElement>().outerHTML
)
}
@Test
fun polylineTest() = runTest {
composition {
Svg {
Polyline(0, 100, 50, 25, 50, 75, 100, 0, attrs = {
attr("stroke", "red")
})
}
}
assertEquals(
"<svg><polyline points=\"0,100 50,25 50,75 100,0\" stroke=\"red\"></polyline></svg>",
nextChild<SVGCircleElement>().outerHTML
)
}
@Test
fun polygonTest() = runTest {
composition {
Svg {
Polygon(0, 100, 50, 25, 50, 75, 100, 0, attrs = {
attr("stroke", "red")
})
}
}
assertEquals(
"<svg><polygon points=\"0,100 50,25 50,75 100,0\" stroke=\"red\"></polygon></svg>",
nextChild<SVGCircleElement>().outerHTML
)
}
@Test
fun linearGradientTest() = runTest {
composition {
Svg {
LinearGradient("myGradient") {
Stop({
attr("offset", 10.percent.toString())
attr("stop-color", "gold")
})
Stop({
attr("offset", 95.percent.toString())
attr("stop-color", "red")
})
}
}
}
assertEquals(
"<svg><linearGradient id=\"myGradient\"><stop offset=\"10%\" stop-color=\"gold\"></stop><stop offset=\"95%\" stop-color=\"red\"></stop></linearGradient></svg>",
nextChild<SVGCircleElement>().outerHTML
)
}
@Test
fun radialGradientTest() = runTest {
composition {
Svg {
RadialGradient("myGradient") {
Stop({
attr("offset", 10.percent.toString())
attr("stop-color", "gold")
})
Stop({
attr("offset", 95.percent.toString())
attr("stop-color", "red")
})
}
}
}
assertEquals(
"<svg><radialGradient id=\"myGradient\"><stop offset=\"10%\" stop-color=\"gold\"></stop><stop offset=\"95%\" stop-color=\"red\"></stop></radialGradient></svg>",
nextChild<SVGCircleElement>().outerHTML
)
}
@Test
fun patternTest() = runTest {
composition {
Svg {
Pattern("something") {
Polygon(0, 100, 50, 25, 50, 75, 100, 0, attrs = {
attr("stroke", "red")
})
}
}
}
assertEquals(
"<svg><pattern id=\"something\"><polygon points=\"0,100 50,25 50,75 100,0\" stroke=\"red\"></polygon></pattern></svg>",
nextChild<SVGCircleElement>().outerHTML
)
}
@Test
fun viewTest() = runTest {
composition {
Svg {
View("one", "0 0 100 100")
}
}
assertEquals(
"<svg><view id=\"one\" viewBox=\"0 0 100 100\"></view></svg>",
nextChild<SVGCircleElement>().outerHTML
)
}
@Test
fun pathTest() = runTest {
composition {
Svg {
Path(
"""
M 10,30
A 20,20 0,0,1 50,30
A 20,20 0,0,1 90,30
Q 90,60 50,90
Q 10,60 10,30 z
""".trimIndent()
)
}
}
assertEquals(
"<svg><path d=\"M 10,30\n" +
"A 20,20 0,0,1 50,30\n" +
"A 20,20 0,0,1 90,30\n" +
"Q 90,60 50,90\n" +
"Q 10,60 10,30 z\"></path></svg>", nextChild<SVGCircleElement>().outerHTML
)
}
@Test
fun useTest() = runTest {
composition {
Svg {
Symbol("myDot", {
attr("width", "10")
attr("height", "10")
attr("viewBox", "0 0 2 2")
}) {
Circle(1.px, 1.px, 1.px)
}
Use("myDot", {
attr("x", "5")
attr("y", "5")
style {
opacity(1)
}
})
}
}
assertEquals(
"<svg><symbol id=\"myDot\" width=\"10\" height=\"10\" viewBox=\"0 0 2 2\"><circle cx=\"1px\" cy=\"1px\" r=\"1px\"></circle></symbol><use href=\"myDot\" x=\"5\" y=\"5\" style=\"opacity: 1;\"></use></svg>",
nextChild<SVGCircleElement>().outerHTML
)
}
@Test
fun svgElementTest() = runTest {
composition {
Svg {
SvgElement<SVGCircleElement>("circle", {
attr("cx", 12.px.toString())
attr("cy", 22.px.toString())
attr("r", 5.percent.toString())
})
}
}
assertEquals(
"<svg><circle cx=\"12px\" cy=\"22px\" r=\"5%\"></circle></svg>",
nextChild<SVGCircleElement>().outerHTML
)
}
@Test
fun svgElementWithViewBoxTest() = runTest {
composition {
Svg(viewBox = "0 0 200 200")
}
assertEquals(
"<svg viewBox=\"0 0 200 200\"></svg>",
nextChild<SVGCircleElement>().outerHTML
)
}
}

1
web/test-utils/src/jsMain/kotlin/org/jetbrains/compose/web/testutils/TestUtils.kt

@ -94,6 +94,7 @@ class TestScope : CoroutineScope by MainScope() {
* Subsequent calls will return next child reference every time.
*/
fun nextChild() = childrenIterator.next() as HTMLElement
fun <T> nextChild() = childrenIterator.next() as T
/**
* @return a reference to current child.

Loading…
Cancel
Save