From 44d927caa0265de3898faa851a14809030b5c983 Mon Sep 17 00:00:00 2001 From: Nikita Gazarov Date: Thu, 16 Jan 2025 11:11:05 +0100 Subject: [PATCH] API: Use by-name arguments for `when` and `whenNot`. - Note: Scala 2 does not support by-name varargs, so in Scala 2 these methods are now limited to at most one argument - This required an alternative implementation vs #169. --- .scalafmt.conf | 1 + .../laminar/api/LaminarPlatformSpecific.scala | 26 +++++++++ .../laminar/api/LaminarPlatformSpecific.scala | 20 +++++++ .../scala/com/raquo/laminar/api/Laminar.scala | 19 +------ .../laminar/tests/basic/ModSpecScala2.scala | 55 ++++++++++++++++++ .../laminar/tests/basic/ModSpecScala3.scala | 56 +++++++++++++++++++ .../raquo/laminar/tests/basic/ModSpec.scala | 41 -------------- 7 files changed, 161 insertions(+), 57 deletions(-) create mode 100644 src/main/scala-2.13/com/raquo/laminar/api/LaminarPlatformSpecific.scala create mode 100644 src/main/scala-3/com/raquo/laminar/api/LaminarPlatformSpecific.scala create mode 100644 src/test/scala-2.13/com/raquo/laminar/tests/basic/ModSpecScala2.scala create mode 100644 src/test/scala-3/com/raquo/laminar/tests/basic/ModSpecScala3.scala diff --git a/.scalafmt.conf b/.scalafmt.conf index e8c1c10..2cf6eb0 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -6,6 +6,7 @@ runner.dialect = "scala213" # - Maybe make a separate config for these files, that has two lines before method names, see https://scalameta.org/scalafmt/docs/configuration.html#fileoverride # - Does this exclusion actually work? IntelliJ seems to ignore it project.excludePaths = [ + "glob:**/src/*/scala-3/**", "glob:**/src/main/scala/com/raquo/laminar/defs/**", "glob:**/project/VersionHelper.scala", ] diff --git a/src/main/scala-2.13/com/raquo/laminar/api/LaminarPlatformSpecific.scala b/src/main/scala-2.13/com/raquo/laminar/api/LaminarPlatformSpecific.scala new file mode 100644 index 0000000..a3cf0bd --- /dev/null +++ b/src/main/scala-2.13/com/raquo/laminar/api/LaminarPlatformSpecific.scala @@ -0,0 +1,26 @@ +package com.raquo.laminar.api + +trait LaminarPlatformSpecific { this: LaminarAliases => + + /** Returns a Modifier that applies another Modifier if `condition` is true + * + * Note: + * - The inner Modifier is evaluated only if `condition` is `true`. + * - Scala 3 version of `when` supports passing multiple modifiers. + */ + def when[El <: Element](condition: Boolean)(mod: => Modifier[El]): Modifier[El] = { + if (condition) { + mod // implicitly converted to a single modifier + } else { + Modifier.empty + } + } + + /** Returns a Modifier that applies another Modifier if `condition` is true. + * + * Note: + * - The inner Modifier is evaluated only if `condition` is `false`. + * - Scala 3 version of `when` supports passing multiple modifiers. + */ + @inline def whenNot[El <: Element](condition: Boolean)(mod: => Modifier[El]): Modifier[El] = when(!condition)(mod) +} diff --git a/src/main/scala-3/com/raquo/laminar/api/LaminarPlatformSpecific.scala b/src/main/scala-3/com/raquo/laminar/api/LaminarPlatformSpecific.scala new file mode 100644 index 0000000..eb1116e --- /dev/null +++ b/src/main/scala-3/com/raquo/laminar/api/LaminarPlatformSpecific.scala @@ -0,0 +1,20 @@ +package com.raquo.laminar.api + +trait LaminarPlatformSpecific { this: LaminarAliases => + + /** Returns a Modifier that applies one or more Modifiers if `condition` is `true`. + * Note: The inner Modifier-s are evaluated only if the condition is `true`. + */ + def when[El <: Element](condition: Boolean)(mods: => Modifier[El]*): Modifier[El] = { + if (condition) { + mods // implicitly converted to a single modifier + } else { + Modifier.empty + } + } + + /** Returns a Modifier that applies one or more modifiers if `condition` is `true`. + * Note: The inner Modifier-s are evaluated only if the condition is `false`. + */ + @inline def whenNot[El <: Element](condition: Boolean)(mods: => Modifier[El]*): Modifier[El] = when(!condition)(mods) +} diff --git a/src/main/scala/com/raquo/laminar/api/Laminar.scala b/src/main/scala/com/raquo/laminar/api/Laminar.scala index 0220556..587cad5 100644 --- a/src/main/scala/com/raquo/laminar/api/Laminar.scala +++ b/src/main/scala/com/raquo/laminar/api/Laminar.scala @@ -1,7 +1,7 @@ package com.raquo.laminar.api import com.raquo.airstream.web.DomEventStream -import com.raquo.laminar.{DomApi, nodes} +import com.raquo.laminar.{nodes, DomApi} import com.raquo.laminar.defs.attrs.{AriaAttrs, HtmlAttrs, SvgAttrs} import com.raquo.laminar.defs.complex.{ComplexHtmlKeys, ComplexSvgKeys} import com.raquo.laminar.defs.eventProps.{DocumentEventProps, GlobalEventProps, WindowEventProps} @@ -19,7 +19,8 @@ import org.scalajs.dom // @TODO[Performance] Check if order of traits matters for quicker access (given trait linearization). Not sure how it's encoded in JS. trait Laminar -extends HtmlTags +extends LaminarPlatformSpecific +with HtmlTags with HtmlAttrs with HtmlProps with GlobalEventProps @@ -251,20 +252,6 @@ with Implicits { // - /** Modifier that applies one or more modifiers if `condition` is true */ - def when[El <: Element](condition: Boolean)(mods: Modifier[El]*): Modifier[El] = { - if (condition) { - mods // implicitly converted to a single modifier - } else { - emptyMod - } - } - - /** Modifier that applies one or more modifiers if `condition` is true */ - @inline def whenNot[El <: Element](condition: Boolean)(mods: Modifier[El]*): Modifier[El] = when(!condition)(mods) - - // - /** Creates controlled input block. * See [[https://laminar.dev/documentation#controlled-inputs Controlled Inputs docs]] */ diff --git a/src/test/scala-2.13/com/raquo/laminar/tests/basic/ModSpecScala2.scala b/src/test/scala-2.13/com/raquo/laminar/tests/basic/ModSpecScala2.scala new file mode 100644 index 0000000..ae6a427 --- /dev/null +++ b/src/test/scala-2.13/com/raquo/laminar/tests/basic/ModSpecScala2.scala @@ -0,0 +1,55 @@ +package com.raquo.laminar.tests.basic + +import com.raquo.laminar.api.L._ + +import com.raquo.laminar.utils.UnitSpec + +class ModSpecScala2 extends UnitSpec { + + it("when keyword") { + + var evaluatedUsed = false + var evaluatedUnused = false + + val el = div( + when(true) { + evaluatedUsed = true + title("foo") + }, + when(false) { + evaluatedUnused = true + title("bar") + }, + when(true)( + height.px(100) + ), + when(true) { + List(minAttr("10"), maxAttr("20")) + }, + when(true)(div("hello")), + when(true) { + onMountInsert(_ => + "world" + ) + }, + when(true) { + text <-- Val("text") + } + ) + + mount(el) + + expectNode(div.of( + title is "foo", + height is "100px", + minAttr is "10", + maxAttr is "20", + div.of("hello"), + "world", + "text" + )) + + assertEquals(evaluatedUsed, true) + assertEquals(evaluatedUnused, false) + } +} diff --git a/src/test/scala-3/com/raquo/laminar/tests/basic/ModSpecScala3.scala b/src/test/scala-3/com/raquo/laminar/tests/basic/ModSpecScala3.scala new file mode 100644 index 0000000..a89b8ed --- /dev/null +++ b/src/test/scala-3/com/raquo/laminar/tests/basic/ModSpecScala3.scala @@ -0,0 +1,56 @@ +package com.raquo.laminar.tests.basic + +import com.raquo.laminar.api.L.{*, given} +import com.raquo.laminar.utils.UnitSpec + +class ModSpecScala3 extends UnitSpec { + + it("when keyword") { + + var evaluatedUsed = false + var evaluatedUnused = false + + val el = div( + when(true) { + evaluatedUsed = true + title("foo") + }, + when(false) { + evaluatedUnused = true + title("bar") + }, + when(true)( + height.px(100), + width.px(200) + ), + when(true) { + List(minAttr("10"), maxAttr("20")) + }, + when(true)(div("hello")), + when(true) { + onMountInsert(_ => + "world" + ) + }, + when(true) { + text <-- Val("text") + } + ) + + mount(el) + + expectNode(div.of( + title is "foo", + height is "100px", + width is "200px", + minAttr is "10", + maxAttr is "20", + div.of("hello"), + "world", + "text" + )) + + assertEquals(evaluatedUsed, true) + assertEquals(evaluatedUnused, false) + } +} diff --git a/src/test/scala/com/raquo/laminar/tests/basic/ModSpec.scala b/src/test/scala/com/raquo/laminar/tests/basic/ModSpec.scala index 0fe95b0..f98f648 100644 --- a/src/test/scala/com/raquo/laminar/tests/basic/ModSpec.scala +++ b/src/test/scala/com/raquo/laminar/tests/basic/ModSpec.scala @@ -7,47 +7,6 @@ import scala.scalajs.js class ModSpec extends UnitSpec { - it("when keyword") { - - val el = div( - when(true) { - title("foo") - }, - when(false) { - title("bar") - }, - when(true)( - height.px(100), - width.px(200) - ), - when(true) { - List(minAttr("10"), maxAttr("20")) - }, - when(true)(div("hello")), - when(true) { - onMountInsert(_ => - "world" - ) - }, - when(true) { - text <-- Val("text") - } - ) - - mount(el) - - expectNode(div.of( - title is "foo", - height is "100px", - width is "200px", - minAttr is "10", - maxAttr is "20", - div.of("hello"), - "world", - "text" - )) - } - it("nodeSeq type inference") { val nodes = nodeSeq("text", span("element"))