diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 1dc41ba10..28d8b9f42 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -16,6 +16,7 @@ jobs:
fail-fast: false
matrix:
java: [11, 17, 21]
+ scala: [2.13.18, 3.3.7]
runs-on: ubuntu-latest
steps:
- name: Checkout
@@ -27,4 +28,4 @@ jobs:
java-version: ${{ matrix.java }}
- uses: sbt/setup-sbt@v1
- name: Build and Test
- run: sbt test
+ run: sbt ++${{ matrix.scala }} test
diff --git a/build.sbt b/build.sbt
index f96b113ab..033c41d99 100644
--- a/build.sbt
+++ b/build.sbt
@@ -63,7 +63,10 @@ lazy val common =
coreProject("common")
.settings(
description := "Common Libraries and Utilities",
- libraryDependencies ++= Seq(slf4j_api, logback, slf4j_log4j12, scala_xml, scala_parser, scalamock)
+ libraryDependencies ++= Seq(slf4j_api, logback, slf4j_log4j12, scala_xml, scala_parser, scalamock, mockito_scalatest(scalaVersion.value)),
+ Test / unmanagedSourceDirectories += {
+ (Test / sourceDirectory).value / ("scala-" + scalaBinaryVersion.value)
+ }
)
lazy val actor =
@@ -71,7 +74,10 @@ lazy val actor =
.dependsOn(common)
.settings(
description := "Simple Actor",
- Test / parallelExecution := false
+ Test / parallelExecution := false,
+ Test / unmanagedSourceDirectories += {
+ (Test / sourceDirectory).value / ("scala-" + scalaBinaryVersion.value)
+ }
)
lazy val markdown =
@@ -102,7 +108,10 @@ lazy val util =
htmlparser,
xerces,
json4s_native,
- )
+ ),
+ Test / unmanagedSourceDirectories += {
+ (Test / sourceDirectory).value / ("scala-" + scalaBinaryVersion.value)
+ }
)
// Web Projects
@@ -115,7 +124,10 @@ lazy val testkit =
.dependsOn(util)
.settings(
description := "Testkit for Webkit Library",
- libraryDependencies ++= Seq(commons_httpclient, servlet_api, json4s_native, json4s_xml)
+ libraryDependencies ++= Seq(commons_httpclient, servlet_api, json4s_native, json4s_xml),
+ Test / unmanagedSourceDirectories += {
+ (Test / sourceDirectory).value / ("scala-" + scalaBinaryVersion.value)
+ }
)
lazy val webkit =
diff --git a/core/actor/src/test/scala/net/liftweb/actor/ActorSpec.scala b/core/actor/src/test/scala-2.13/net/liftweb/actor/ActorSpec.scala
similarity index 100%
rename from core/actor/src/test/scala/net/liftweb/actor/ActorSpec.scala
rename to core/actor/src/test/scala-2.13/net/liftweb/actor/ActorSpec.scala
diff --git a/core/actor/src/test/scala/net/liftweb/actor/LAFutureSpec.scala b/core/actor/src/test/scala-2.13/net/liftweb/actor/LAFutureSpec.scala
similarity index 100%
rename from core/actor/src/test/scala/net/liftweb/actor/LAFutureSpec.scala
rename to core/actor/src/test/scala-2.13/net/liftweb/actor/LAFutureSpec.scala
diff --git a/core/actor/src/test/scala/net/liftweb/actor/MockLiftActorSpec.scala b/core/actor/src/test/scala-2.13/net/liftweb/actor/MockLiftActorSpec.scala
similarity index 100%
rename from core/actor/src/test/scala/net/liftweb/actor/MockLiftActorSpec.scala
rename to core/actor/src/test/scala-2.13/net/liftweb/actor/MockLiftActorSpec.scala
diff --git a/core/actor/src/test/scala-3/net/liftweb/actor/ActorSpec.scala b/core/actor/src/test/scala-3/net/liftweb/actor/ActorSpec.scala
new file mode 100644
index 000000000..f23a41f01
--- /dev/null
+++ b/core/actor/src/test/scala-3/net/liftweb/actor/ActorSpec.scala
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2011 WorldWide Conferencing, LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.liftweb
+package actor
+
+import org.specs2.mutable.Specification
+import scala.concurrent.duration._
+import common._
+
+
+/**
+ * Systems under specification for Lift Actor.
+ */
+class ActorSpec extends Specification {
+ "Actor Specification".title
+ sequential
+
+ "A Scala Actor" should {
+ "support common features" in commonFeatures(new MyScalaActor)
+ }
+
+ private def commonFeatures(actor: LiftActor) = {
+ sequential
+
+ "allow setting and getting of a value" in {
+ val a = actor
+ a ! Set(33)
+ a !? Get()
+ Thread.sleep(100)
+ (a.!?(50, Get())) must beEqualTo(Full(Answer(33)))
+ }
+
+ "allow setting and getting of a value with subclass of Get()" in {
+ val a = actor
+ a ! Set(33)
+ a ! new FunnyGet()
+ Thread.sleep(100)
+ (a.!?(50L, new FunnyGet())) must beEqualTo(Full(Answer(33)))
+ }
+
+ "allow adding of a value" in {
+ val a = actor
+ a ! Set(33)
+ (a !< Add(44)).get(500) must beEqualTo(Full(Answer(77)))
+ }
+
+ "allow subtracting of a value" in {
+ val a = actor
+ a ! Set(33)
+ (a !< Sub(11)).get(500) must beEqualTo(Full(Answer(22)))
+ }
+
+ "properly timeout" in {
+ val a = actor
+ Thread.sleep(100)
+ (a !< Set(33)).get(50) must beEqualTo(Empty)
+ }
+ }
+
+}
+
+
+case class Add(num: Int)
+case class Sub(num: Int)
+case class Set(num: Int)
+
+case class Get()
+class FunnyGet() extends Get()
+
+case class Answer(num: Int)
diff --git a/core/actor/src/test/scala-3/net/liftweb/actor/LAFutureSpec.scala b/core/actor/src/test/scala-3/net/liftweb/actor/LAFutureSpec.scala
new file mode 100644
index 000000000..4945fc99f
--- /dev/null
+++ b/core/actor/src/test/scala-3/net/liftweb/actor/LAFutureSpec.scala
@@ -0,0 +1,145 @@
+package net.liftweb.actor
+
+import net.liftweb.common.{Box, Failure, Full}
+import org.specs2.mutable.Specification
+import java.util.concurrent.atomic.AtomicBoolean
+
+class LAFutureSpec extends Specification {
+ sequential
+ val timeout = 20000L
+ LAScheduler
+
+ "LAFuture" should {
+ val futureSpecScheduler = new LAScheduler {
+ override def execute(f: ()=>Unit): Unit = f()
+ }
+
+ "map to failing future if transforming function throws an Exception" in {
+ val future = LAFuture(() => 1, futureSpecScheduler)
+ def tranformThrowingException(input: Int) = {
+ throw new Exception("fail")
+ }
+
+ val transformedFuture = future.map(tranformThrowingException)
+
+ var notifiedAboutFailure: Boolean = false
+ transformedFuture.onFail { _ =>
+ notifiedAboutFailure = true
+ }
+
+ transformedFuture.get(timeout)
+ notifiedAboutFailure must beTrue
+ }
+
+ "flatMap to failing future if transforming function throws an Exception" in {
+ val future = LAFuture(() => 1, futureSpecScheduler)
+ def tranformThrowingException(input: Int): LAFuture[Int] = {
+ throw new Exception("fail")
+ }
+
+ val transformedFuture = future.flatMap(tranformThrowingException)
+
+ var notifiedAboutFailure: Boolean = false
+ transformedFuture.onFail { _ =>
+ notifiedAboutFailure = true
+ }
+
+ transformedFuture.get(timeout)
+ notifiedAboutFailure must beTrue
+ }
+
+ "return original Failure after timeout" in {
+ val future = new LAFuture()
+ val givenFailure = Failure("fooFailure")
+ LAScheduler.execute { () =>
+ Thread.sleep(500)
+ future.fail(givenFailure)
+ }
+
+ val result = future.get(timeout)
+
+ result must beEqualTo(givenFailure)
+ }
+
+ "when collecting results with LAFuture.collect" in {
+ "collect one future result" in {
+ val givenOneResult = 123
+ val one = LAFuture(() => givenOneResult)
+ LAFuture.collect(one).get(timeout) must beEqualTo(List(givenOneResult))
+ }
+
+ "collect more future results in correct order" in {
+ val givenOneResult = 123
+ val givenTwoResult = 234
+ val one = LAFuture(() => givenOneResult)
+ val two = LAFuture(() => givenTwoResult)
+ LAFuture.collect(one, two).get(timeout) must beEqualTo(List(givenOneResult, givenTwoResult))
+ }
+
+ "collect empty list immediately" in {
+ val collectResult = LAFuture.collect(Nil: _*)
+ collectResult.isSatisfied must beTrue
+ collectResult.get(timeout) must beEqualTo(Nil)
+ }
+
+ "report a failed LAFuture as a failure for the overall future" in {
+ val one: LAFuture[Int] = new LAFuture
+ val two: LAFuture[Int] = LAFuture(() => 5)
+
+ one.fail(Failure("boom boom boom!"))
+
+ val collectResult = LAFuture.collect(one, two)
+ collectResult.get(timeout) must beEqualTo(Failure("boom boom boom!"))
+ }
+ }
+
+ "when collecting Boxed results with collectAll" in {
+ "collectAll collects an EmptyBox immediately" in {
+ val one: LAFuture[Box[Int]] = LAFuture(() => { Failure("whoops"): Box[Int] })
+ val two: LAFuture[Box[Int]] = LAFuture(() => { Thread.sleep(10000); Full(1) })
+
+ val collectResult = LAFuture.collectAll(one, two)
+ collectResult.get(5000) must beEqualTo(Failure("whoops"))
+ }
+
+ "collectAll collects a set of Fulls" in {
+ val one: LAFuture[Box[Int]] = LAFuture(() => Full(1): Box[Int])
+ val two: LAFuture[Box[Int]] = LAFuture(() => Full(2): Box[Int])
+ val three: LAFuture[Box[Int]] = LAFuture(() => Full(3): Box[Int])
+
+ val collectResult = LAFuture.collectAll(one, two, three)
+ collectResult.get(timeout) should beLike {
+ case Full(Full(results)) =>
+ results should contain(allOf(1, 2, 3))
+ }
+ }
+
+ "collectAll reports a failed LAFuture as a failure for the overall future" in {
+ val one: LAFuture[Box[Int]] = new LAFuture
+ val two: LAFuture[Box[Int]] = LAFuture(() => Full(5): Box[Int])
+
+ one.fail(Failure("boom boom boom!"))
+
+ val collectResult = LAFuture.collectAll(one, two)
+ collectResult.get(timeout) must beEqualTo(Failure("boom boom boom!"))
+ }
+
+ "collectAll empty list immediately" in {
+ val collectResult = LAFuture.collectAll(Nil : _*)
+ collectResult.isSatisfied must beTrue
+ collectResult.get(timeout) must beEqualTo(Nil)
+ }
+
+ "report a failed LAFuture as a failure for the overall future" in {
+ val one: LAFuture[Box[Int]] = new LAFuture
+ val two: LAFuture[Box[Int]] = LAFuture(() => Full(5): Box[Int])
+
+ one.fail(Failure("boom boom boom!"))
+
+ val collectResult = LAFuture.collectAll(one, two)
+ collectResult.get(timeout) must beEqualTo(Failure("boom boom boom!"))
+ }
+ }
+ }
+
+}
diff --git a/core/actor/src/test/scala-3/net/liftweb/actor/MockLiftActorSpec.scala b/core/actor/src/test/scala-3/net/liftweb/actor/MockLiftActorSpec.scala
new file mode 100644
index 000000000..575f9b386
--- /dev/null
+++ b/core/actor/src/test/scala-3/net/liftweb/actor/MockLiftActorSpec.scala
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2011 WorldWide Conferencing, LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.liftweb
+package actor
+
+import org.specs2.mutable.Specification
+
+class MockLiftActorSpec extends Specification {
+ "Mock Actor Specification".title
+
+ sealed trait MockSpecActorMessage
+ case object MockSpecActorMessage1 extends MockSpecActorMessage
+ case object MockSpecActorMessage2 extends MockSpecActorMessage
+ case object MockSpecActorMessage3 extends MockSpecActorMessage
+
+ "A MockSpecializedLiftActor" should {
+ "correctly indicate when it has received a message" in {
+ val mockActor = new MockSpecializedLiftActor[MockSpecActorMessage]
+
+ mockActor ! MockSpecActorMessage1
+ mockActor ! MockSpecActorMessage2
+
+ mockActor.hasReceivedMessage_?(MockSpecActorMessage1) must beTrue
+ }
+
+ "correctly indicate when it has not received a message" in {
+ val mockActor = new MockSpecializedLiftActor[MockSpecActorMessage]
+
+ mockActor ! MockSpecActorMessage1
+ mockActor ! MockSpecActorMessage2
+
+ mockActor.hasReceivedMessage_?(MockSpecActorMessage3) must beFalse
+ }
+
+ "correctly indicate the number of messages it has received" in {
+ val mockActor = new MockSpecializedLiftActor[MockSpecActorMessage]
+
+ mockActor ! MockSpecActorMessage1
+ mockActor ! MockSpecActorMessage2
+ mockActor ! MockSpecActorMessage3
+
+ mockActor.messageCount === 3
+ }
+
+ "correctly list the messages it has received" in {
+ val mockActor = new MockSpecializedLiftActor[MockSpecActorMessage]
+
+ mockActor ! MockSpecActorMessage1
+ mockActor ! MockSpecActorMessage2
+ mockActor ! MockSpecActorMessage3
+
+ mockActor.messages === List(
+ MockSpecActorMessage3,
+ MockSpecActorMessage2,
+ MockSpecActorMessage1
+ )
+ }
+ }
+
+ "A MockLiftActor" should {
+ "correctly indicate when it has received a message" in {
+ val mockActor = new MockLiftActor
+
+ mockActor ! MockSpecActorMessage1
+ mockActor ! MockSpecActorMessage2
+
+ mockActor.hasReceivedMessage_?(MockSpecActorMessage1) must beTrue
+ }
+
+ "correctly indicate when it has not received a message" in {
+ val mockActor = new MockLiftActor
+
+ mockActor ! MockSpecActorMessage1
+ mockActor ! MockSpecActorMessage2
+
+ mockActor.hasReceivedMessage_?(MockSpecActorMessage3) must beFalse
+ }
+
+ "correctly indicate the number of messages it has received" in {
+ val mockActor = new MockLiftActor
+
+ mockActor ! MockSpecActorMessage1
+ mockActor ! MockSpecActorMessage2
+ mockActor ! MockSpecActorMessage3
+
+ mockActor.messageCount === 3
+ }
+
+ "correctly list the messages it has received" in {
+ val mockActor = new MockLiftActor
+
+ mockActor ! MockSpecActorMessage1
+ mockActor ! MockSpecActorMessage2
+ mockActor ! MockSpecActorMessage3
+
+ mockActor.messages === List(
+ MockSpecActorMessage3,
+ MockSpecActorMessage2,
+ MockSpecActorMessage1
+ )
+ }
+ }
+}
diff --git a/core/common/src/test/scala/net/liftweb/common/BoxLoggingSpec.scala b/core/common/src/test/scala-2.13/net/liftweb/common/BoxLoggingSpec.scala
similarity index 100%
rename from core/common/src/test/scala/net/liftweb/common/BoxLoggingSpec.scala
rename to core/common/src/test/scala-2.13/net/liftweb/common/BoxLoggingSpec.scala
diff --git a/core/common/src/test/scala/net/liftweb/common/BoxSpec.scala b/core/common/src/test/scala-2.13/net/liftweb/common/BoxSpec.scala
similarity index 100%
rename from core/common/src/test/scala/net/liftweb/common/BoxSpec.scala
rename to core/common/src/test/scala-2.13/net/liftweb/common/BoxSpec.scala
diff --git a/core/common/src/test/scala/net/liftweb/common/ConversionsSpec.scala b/core/common/src/test/scala-2.13/net/liftweb/common/ConversionsSpec.scala
similarity index 100%
rename from core/common/src/test/scala/net/liftweb/common/ConversionsSpec.scala
rename to core/common/src/test/scala-2.13/net/liftweb/common/ConversionsSpec.scala
diff --git a/core/common/src/test/scala/net/liftweb/common/HListSpec.scala b/core/common/src/test/scala-2.13/net/liftweb/common/HListSpec.scala
similarity index 100%
rename from core/common/src/test/scala/net/liftweb/common/HListSpec.scala
rename to core/common/src/test/scala-2.13/net/liftweb/common/HListSpec.scala
diff --git a/core/common/src/test/scala/net/liftweb/common/LoggingSpec.scala b/core/common/src/test/scala-2.13/net/liftweb/common/LoggingSpec.scala
similarity index 100%
rename from core/common/src/test/scala/net/liftweb/common/LoggingSpec.scala
rename to core/common/src/test/scala-2.13/net/liftweb/common/LoggingSpec.scala
diff --git a/core/common/src/test/scala/net/liftweb/common/LruMapSpec.scala b/core/common/src/test/scala-2.13/net/liftweb/common/LruMapSpec.scala
similarity index 100%
rename from core/common/src/test/scala/net/liftweb/common/LruMapSpec.scala
rename to core/common/src/test/scala-2.13/net/liftweb/common/LruMapSpec.scala
diff --git a/core/common/src/test/scala-3/net/liftweb/common/BoxLoggingSpec.scala b/core/common/src/test/scala-3/net/liftweb/common/BoxLoggingSpec.scala
new file mode 100644
index 000000000..ba5161e2c
--- /dev/null
+++ b/core/common/src/test/scala-3/net/liftweb/common/BoxLoggingSpec.scala
@@ -0,0 +1,457 @@
+package net.liftweb
+package common
+
+import org.slf4j.{Logger=>SLF4JLogger}
+
+// MockContext not available in Scala 3
+import org.specs2.mutable.Specification
+import org.mockito.Mockito.{mock, when, verify}
+import org.mockito.ArgumentMatchers.{any, anyString}
+
+class BoxLoggingSpec extends Specification {
+ class MockBoxLoggingClass extends BoxLogging {
+ var loggedErrors = List[(String, Option[Throwable])]()
+ var loggedWarns = List[(String, Option[Throwable])]()
+ var loggedInfos = List[(String, Option[Throwable])]()
+ var loggedDebugs = List[(String, Option[Throwable])]()
+ var loggedTraces = List[(String, Option[Throwable])]()
+
+ protected def logBoxError(message: String, throwable: Option[Throwable]): Unit = {
+ loggedErrors ::= (message, throwable)
+ }
+ protected def logBoxWarn(message: String, throwable: Option[Throwable]): Unit = {
+ loggedWarns ::= (message, throwable)
+ }
+ protected def logBoxInfo(message: String, throwable: Option[Throwable]): Unit = {
+ loggedInfos ::= (message, throwable)
+ }
+ protected def logBoxDebug(message: String, throwable: Option[Throwable]): Unit = {
+ loggedDebugs ::= (message, throwable)
+ }
+ protected def logBoxTrace(message: String, throwable: Option[Throwable]): Unit = {
+ loggedTraces ::= (message, throwable)
+ }
+ }
+
+ "BoxLogging" should {
+ "when logging empty boxes" in {
+ def verifyContentList(list: List[(String, Option[Throwable])]) = {
+ list must beLike {
+ case (paramFailure4, None) ::
+ (paramFailure3, None) ::
+ (paramFailure2, None) ::
+ (paramFailure1, Some(paramExp)) ::
+ (chained1, None) ::
+ (chained2, None) ::
+ (level1, None) ::
+ (level2, Some(exp2)) ::
+ (level3, None) ::
+ (level4, Some(exp4)) ::
+ (emptyMessage, None) ::
+ (fullParamMessage, Some(paramException)) ::
+ (paramMessage, None) ::
+ (exceptedMessage, Some(failureException)) ::
+ (failureMessage, None) ::
+ Nil =>
+ (failureMessage must startWith("Second")) and
+ (failureMessage must contain("Failed")) and
+ (exceptedMessage must startWith("Third")) and
+ (exceptedMessage must contain("Excepted")) and
+ (failureException.getMessage === "uh-oh") and
+ (paramMessage must startWith("Fourth")) and
+ (paramMessage must contain("ParamFailed")) and
+ (paramMessage must contain("BoxLoggingSpec")) and
+ (fullParamMessage must startWith("Fifth")) and
+ (fullParamMessage must contain("ParamExcepted")) and
+ (fullParamMessage must contain("BoxLoggingSpec")) and
+ (paramException.getMessage === "param uh-oh") and
+ (emptyMessage must startWith("Sixth")) and
+ (emptyMessage must contain("Empty")) and
+ (level1 must contain("Failure level 3 caused by: Failure level 4")) and
+ (level2 must contain("Failure level 2 caused by: Failure level 3")) and
+ (exp2 must beAnInstanceOf[IllegalArgumentException]) and
+ (level3 must contain("Failure level 1 caused by: Failure level 2")) and
+ (level4 must contain("Multilevel failure: Failure level 1")) and
+ (exp4 must beAnInstanceOf[NullPointerException]) and
+ (chained1 must contain("Chained failure caused by: Boom")) and
+ (chained2 must contain("Chain all failures: Chained failure")) and
+ (paramFailure4 must contain("Param Failure lvl 3 with param Param 3 caused by: Param Failure lvl 4 with param Param 4")) and
+ (paramFailure3 must contain("Failure lvl 2 caused by: Param Failure lvl 3 with param Param 3")) and
+ (paramFailure2 must contain("Param Failure lvl 1 with param Param 1 caused by: Failure lvl 2")) and
+ (paramFailure1 must contain("Param failure: Param Failure lvl 1 with param Param 1")) and
+ (paramExp must beAnInstanceOf[IllegalArgumentException])
+ }
+ }
+
+ "log correctly on ERROR level" in {
+ val results =
+ new MockBoxLoggingClass {
+ Full("Not empty").logEmptyBox("First")
+ Failure("Failed").logEmptyBox("Second")
+ Failure("Excepted", Full(new Exception("uh-oh")), Empty).logEmptyBox("Third")
+ ParamFailure("ParamFailed", this).logEmptyBox("Fourth")
+ ParamFailure(
+ "ParamExcepted",
+ Full(new Exception("param uh-oh")),
+ Empty,
+ this
+ ).logEmptyBox("Fifth")
+ (Empty).logEmptyBox("Sixth")
+ Failure(
+ "Failure level 1", Full(new NullPointerException), Full(Failure(
+ "Failure level 2", Empty, Full(Failure(
+ "Failure level 3", Full(new IllegalArgumentException), Full(Failure(
+ "Failure level 4"
+ )))
+ ))
+ )
+ ).logEmptyBox("Multilevel failure")
+ (Failure("Boom") ?~! "Chained failure").logEmptyBox("Chain all failures")
+ ParamFailure(
+ "Param Failure lvl 1", Full(new IllegalArgumentException), Full(Failure(
+ "Failure lvl 2", Empty, Full(ParamFailure(
+ "Param Failure lvl 3", Empty, Full(ParamFailure(
+ "Param Failure lvl 4",
+ "Param 4"
+ )),
+ "Param 3"
+ ))
+ )),
+ "Param 1"
+ ).logEmptyBox("Param failure")
+ }
+
+ verifyContentList(results.loggedErrors)
+ }
+
+ "log correctly on WARN level" in {
+ val results =
+ new MockBoxLoggingClass {
+ Full("Not empty").warnLogEmptyBox("First")
+ Failure("Failed").warnLogEmptyBox("Second")
+ Failure("Excepted", Full(new Exception("uh-oh")), Empty).warnLogEmptyBox("Third")
+ ParamFailure("ParamFailed", this).warnLogEmptyBox("Fourth")
+ ParamFailure(
+ "ParamExcepted",
+ Full(new Exception("param uh-oh")),
+ Empty,
+ this
+ ).warnLogEmptyBox("Fifth")
+ (Empty).warnLogEmptyBox("Sixth")
+ Failure(
+ "Failure level 1", Full(new NullPointerException), Full(Failure(
+ "Failure level 2", Empty, Full(Failure(
+ "Failure level 3", Full(new IllegalArgumentException), Full(Failure(
+ "Failure level 4"
+ )))
+ ))
+ )
+ ).warnLogEmptyBox("Multilevel failure")
+ (Failure("Boom") ?~! "Chained failure").warnLogEmptyBox("Chain all failures")
+ ParamFailure(
+ "Param Failure lvl 1", Full(new IllegalArgumentException), Full(Failure(
+ "Failure lvl 2", Empty, Full(ParamFailure(
+ "Param Failure lvl 3", Empty, Full(ParamFailure(
+ "Param Failure lvl 4",
+ "Param 4"
+ )),
+ "Param 3"
+ ))
+ )),
+ "Param 1"
+ ).warnLogEmptyBox("Param failure")
+ }
+
+ verifyContentList(results.loggedWarns)
+ }
+
+ "log correctly on INFO level" in {
+ val results =
+ new MockBoxLoggingClass {
+ Full("Not empty").infoLogEmptyBox("First")
+ Failure("Failed").infoLogEmptyBox("Second")
+ Failure("Excepted", Full(new Exception("uh-oh")), Empty).infoLogEmptyBox("Third")
+ ParamFailure("ParamFailed", this).infoLogEmptyBox("Fourth")
+ ParamFailure(
+ "ParamExcepted",
+ Full(new Exception("param uh-oh")),
+ Empty,
+ this
+ ).infoLogEmptyBox("Fifth")
+ (Empty).infoLogEmptyBox("Sixth")
+ Failure(
+ "Failure level 1", Full(new NullPointerException), Full(Failure(
+ "Failure level 2", Empty, Full(Failure(
+ "Failure level 3", Full(new IllegalArgumentException), Full(Failure(
+ "Failure level 4"
+ )))
+ ))
+ )
+ ).infoLogEmptyBox("Multilevel failure")
+ (Failure("Boom") ?~! "Chained failure").infoLogEmptyBox("Chain all failures")
+ ParamFailure(
+ "Param Failure lvl 1", Full(new IllegalArgumentException), Full(Failure(
+ "Failure lvl 2", Empty, Full(ParamFailure(
+ "Param Failure lvl 3", Empty, Full(ParamFailure(
+ "Param Failure lvl 4",
+ "Param 4"
+ )),
+ "Param 3"
+ ))
+ )),
+ "Param 1"
+ ).infoLogEmptyBox("Param failure")
+ }
+
+ verifyContentList(results.loggedInfos)
+ }
+
+ "log correctly on DEBUG level" in {
+ val results =
+ new MockBoxLoggingClass {
+ Full("Not empty").debugLogEmptyBox("First")
+ Failure("Failed").debugLogEmptyBox("Second")
+ Failure("Excepted", Full(new Exception("uh-oh")), Empty).debugLogEmptyBox("Third")
+ ParamFailure("ParamFailed", this).debugLogEmptyBox("Fourth")
+ ParamFailure(
+ "ParamExcepted",
+ Full(new Exception("param uh-oh")),
+ Empty,
+ this
+ ).debugLogEmptyBox("Fifth")
+ (Empty).debugLogEmptyBox("Sixth")
+ Failure(
+ "Failure level 1", Full(new NullPointerException), Full(Failure(
+ "Failure level 2", Empty, Full(Failure(
+ "Failure level 3", Full(new IllegalArgumentException), Full(Failure(
+ "Failure level 4"
+ )))
+ ))
+ )
+ ).debugLogFailure("Multilevel failure")
+ (Failure("Boom") ?~! "Chained failure").debugLogFailure("Chain all failures")
+ ParamFailure(
+ "Param Failure lvl 1", Full(new IllegalArgumentException), Full(Failure(
+ "Failure lvl 2", Empty, Full(ParamFailure(
+ "Param Failure lvl 3", Empty, Full(ParamFailure(
+ "Param Failure lvl 4",
+ "Param 4"
+ )),
+ "Param 3"
+ ))
+ )),
+ "Param 1"
+ ).debugLogFailure("Param failure")
+ }
+
+ verifyContentList(results.loggedDebugs)
+ }
+
+ "log correctly on TRACE level" in {
+ val results =
+ new MockBoxLoggingClass {
+ Full("Not empty").traceLogEmptyBox("First")
+ Failure("Failed").traceLogEmptyBox("Second")
+ Failure("Excepted", Full(new Exception("uh-oh")), Empty).traceLogEmptyBox("Third")
+ ParamFailure("ParamFailed", this).traceLogEmptyBox("Fourth")
+ ParamFailure(
+ "ParamExcepted",
+ Full(new Exception("param uh-oh")),
+ Empty,
+ this
+ ).traceLogEmptyBox("Fifth")
+ (Empty).traceLogEmptyBox("Sixth")
+ Failure(
+ "Failure level 1", Full(new NullPointerException), Full(Failure(
+ "Failure level 2", Empty, Full(Failure(
+ "Failure level 3", Full(new IllegalArgumentException), Full(Failure(
+ "Failure level 4"
+ )))
+ ))
+ )
+ ).traceLogEmptyBox("Multilevel failure")
+ (Failure("Boom") ?~! "Chained failure").traceLogEmptyBox("Chain all failures")
+ ParamFailure(
+ "Param Failure lvl 1", Full(new IllegalArgumentException), Full(Failure(
+ "Failure lvl 2", Empty, Full(ParamFailure(
+ "Param Failure lvl 3", Empty, Full(ParamFailure(
+ "Param Failure lvl 4",
+ "Param 4"
+ )),
+ "Param 3"
+ ))
+ )),
+ "Param 1"
+ ).traceLogEmptyBox("Param failure")
+ }
+
+ verifyContentList(results.loggedTraces)
+ }
+ }
+
+ "when logging only failures" in {
+ def verifyContentList(list: List[(String, Option[Throwable])]) = {
+ list must beLike {
+ case (fullParamMessage, Some(paramException)) ::
+ (paramMessage, None) ::
+ (exceptedMessage, Some(failureException)) ::
+ (failureMessage, None) ::
+ Nil =>
+ (failureMessage must startWith("Second")) and
+ (failureMessage must contain("Failed")) and
+ (exceptedMessage must startWith("Third")) and
+ (exceptedMessage must contain("Excepted")) and
+ (failureException.getMessage === "uh-oh") and
+ (paramMessage must startWith("Fourth")) and
+ (paramMessage must contain("ParamFailed")) and
+ (paramMessage must contain("BoxLoggingSpec")) and
+ (fullParamMessage must startWith("Fifth")) and
+ (fullParamMessage must contain("ParamExcepted")) and
+ (fullParamMessage must contain("BoxLoggingSpec")) and
+ (paramException.getMessage === "param uh-oh")
+ }
+ }
+
+ "log correctly on ERROR level" in {
+ val results =
+ new MockBoxLoggingClass {
+ Full("Not empty").logFailure("First")
+ Failure("Failed").logFailure("Second")
+ Failure("Excepted", Full(new Exception("uh-oh")), Empty).logFailure("Third")
+ ParamFailure("ParamFailed", this).logFailure("Fourth")
+ ParamFailure(
+ "ParamExcepted",
+ Full(new Exception("param uh-oh")),
+ Empty,
+ this
+ ).logFailure("Fifth")
+ (Empty).logFailure("Sixth")
+ }
+
+ verifyContentList(results.loggedErrors)
+ }
+
+ "log correctly on WARN level" in {
+ val results =
+ new MockBoxLoggingClass {
+ Full("Not empty").warnLogFailure("First")
+ Failure("Failed").warnLogFailure("Second")
+ Failure("Excepted", Full(new Exception("uh-oh")), Empty).warnLogFailure("Third")
+ ParamFailure("ParamFailed", this).warnLogFailure("Fourth")
+ ParamFailure(
+ "ParamExcepted",
+ Full(new Exception("param uh-oh")),
+ Empty,
+ this
+ ).warnLogFailure("Fifth")
+ (Empty).warnLogFailure("Sixth")
+ }
+
+ verifyContentList(results.loggedWarns)
+ }
+
+ "log correctly on INFO level" in {
+ val results =
+ new MockBoxLoggingClass {
+ Full("Not empty").infoLogFailure("First")
+ Failure("Failed").infoLogFailure("Second")
+ Failure("Excepted", Full(new Exception("uh-oh")), Empty).infoLogFailure("Third")
+ ParamFailure("ParamFailed", this).infoLogFailure("Fourth")
+ ParamFailure(
+ "ParamExcepted",
+ Full(new Exception("param uh-oh")),
+ Empty,
+ this
+ ).infoLogFailure("Fifth")
+ (Empty).infoLogFailure("Sixth")
+ }
+
+ verifyContentList(results.loggedInfos)
+ }
+
+ "log correctly on DEBUG level" in {
+ val results =
+ new MockBoxLoggingClass {
+ Full("Not empty").debugLogFailure("First")
+ Failure("Failed").debugLogFailure("Second")
+ Failure("Excepted", Full(new Exception("uh-oh")), Empty).debugLogFailure("Third")
+ ParamFailure("ParamFailed", this).debugLogFailure("Fourth")
+ ParamFailure(
+ "ParamExcepted",
+ Full(new Exception("param uh-oh")),
+ Empty,
+ this
+ ).debugLogFailure("Fifth")
+ (Empty).debugLogFailure("Sixth")
+ }
+
+ verifyContentList(results.loggedDebugs)
+ }
+
+ "log correctly on TRACE level" in {
+ val results =
+ new MockBoxLoggingClass {
+ Full("Not empty").traceLogFailure("First")
+ Failure("Failed").traceLogFailure("Second")
+ Failure("Excepted", Full(new Exception("uh-oh")), Empty).traceLogFailure("Third")
+ ParamFailure("ParamFailed", this).traceLogFailure("Fourth")
+ ParamFailure(
+ "ParamExcepted",
+ Full(new Exception("param uh-oh")),
+ Empty,
+ this
+ ).traceLogFailure("Fifth")
+ (Empty).traceLogFailure("Sixth")
+ }
+
+ verifyContentList(results.loggedTraces)
+ }
+ }
+
+ "when logging in a Loggable" in {
+ import net.liftweb.common.Logger
+ import org.slf4j.{Logger => SLF4JLogger}
+
+ "log to the Lift logger" in {
+ val mockLogger = mock(classOf[SLF4JLogger])
+ when(mockLogger.isErrorEnabled()).thenReturn(true)
+
+ class MyLoggable extends LoggableBoxLogging {
+ override val logger = new Logger {
+ override protected def _logger = mockLogger
+ }
+ }
+
+ val result =
+ new MyLoggable {
+ Failure("Failed").logFailure("Second")
+ Failure("Excepted", Full(new Exception("uh-oh")), Empty).logFailure("Third")
+ }
+
+ verify(mockLogger).error(anyString())
+ verify(mockLogger).error(anyString(), any(classOf[Throwable]))
+ }
+ }
+
+ "when logging with in SLF4J context" in {
+ import org.slf4j.{Logger => SLF4JLogger}
+
+ "log to the SLF4J logger" in {
+ val mockLogger = mock(classOf[SLF4JLogger])
+ when(mockLogger.isErrorEnabled()).thenReturn(true)
+
+ class TestClass extends SLF4JBoxLogging {
+ val logger = mockLogger
+ }
+
+ new TestClass {
+ Failure("Failed").logFailure("Second")
+ Failure("Excepted", Full(new Exception("uh-oh")), Empty).logFailure("Third")
+ }
+
+ verify(mockLogger).error(anyString())
+ verify(mockLogger).error(anyString(), any(classOf[Throwable]))
+ }
+ }
+ }
+}
diff --git a/core/common/src/test/scala-3/net/liftweb/common/BoxSpec.scala b/core/common/src/test/scala-3/net/liftweb/common/BoxSpec.scala
new file mode 100644
index 000000000..408588530
--- /dev/null
+++ b/core/common/src/test/scala-3/net/liftweb/common/BoxSpec.scala
@@ -0,0 +1,583 @@
+/*
+ * Copyright 2007-2011 WorldWide Conferencing, LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.liftweb
+package common
+
+import org.specs2.mutable.Specification
+import org.specs2.ScalaCheck
+import org.specs2.collection.canEqualAny
+import org.scalacheck.{Arbitrary, Gen}
+import Gen._
+
+import Box._
+
+
+/* commented out because it tests the compilation phase and we want the compiler to "do the right thing"
+class TypeBoundsTest extends Specification with ScalaCheck {
+ "Type Bounds Spec".title
+
+ "Type bounds" can {
+ "do type testing" in {
+ def foo[T: ExcludeThisType.exclude[Nothing]#other](a: T) = a.toString
+ foo(33.0)
+ foo(throw new Exception("foo"))
+
+ true == true
+ }
+ }
+}
+
+*/
+
+/**
+ * System under specification for Box.
+ */
+class BoxSpec extends Specification with ScalaCheck with BoxGenerator {
+
+ "A Box" can {
+ "be created from a Option. It is Empty if the option is None" in {
+ Box(None) must beEqualTo(Empty)
+ }
+ "be created from a Option. It is Full(x) if the option is Some(x)" in {
+ Box(Some(1)) must beEqualTo(Full(1))
+ }
+ "be created from a List containing one element. It is Empty if the list is empty" in {
+ Box(Nil) must beEqualTo(Empty)
+ }
+ "be created from a List containing one element. It is Full(x) if the list is List(x)" in {
+ Box(List(1)) must beEqualTo(Full(1))
+ }
+ "be created from a List containing more than one element. It is Full(x) if the list is x::rest" in {
+ Box(List(1, 2, 3)) must beEqualTo(Full(1))
+ }
+ "be used as an iterable" in {
+ (Full(1) reduceLeft {(x: Int, y: Int) => x + y}) === 1
+ }
+ "be used as an Option" in {
+ Full(1) orElse Some(2) must beSome(1)
+ Empty orElse Some(2) must beSome(2)
+ }
+ "be implicitly defined from an Option. The openOrThrowException method can be used on an Option for example" in {
+ Some(1).openOrThrowException("This is a test") === 1
+ }
+ "be defined from some legacy code (possibly passing null values). If the passed value is not null, a Full(value) is returned" in {
+ Box.legacyNullTest("s") must beEqualTo(Full("s"))
+ }
+ "be defined from some legacy code (possibly passing null values). If the passed value is null, an Empty is returned" in {
+ Box.legacyNullTest(null) must beEqualTo(Empty)
+ }
+ }
+
+ "A Box" should {
+ "provide a 'choice' method to either apply a function to the Box value or return another default can" in {
+ def gotIt = (x: Int) => Full("got it: " + x.toString)
+
+ Full(1).choice(gotIt)(Full("nothing")) must beEqualTo(Full("got it: 1"))
+ Empty.choice(gotIt)(Full("nothing")) must beEqualTo(Full("nothing"))
+ }
+ }
+
+ "A Full Box" should {
+ "not beEmpty" in {
+ Full(1).isEmpty must beFalse
+ }
+ "be defined" in {
+ Full(1).isDefined must beTrue
+ }
+ "return its value when opened" in {
+ Full(1).openOrThrowException("This is a test") === 1
+ }
+ "return its value when opened with openOr(default value)" in {
+ (Full(1) openOr 0) === 1
+ }
+ "return itself when or'ed with another Box" in {
+ (Full(1) or Full(2)) must beEqualTo(Full(1))
+ }
+ "define an 'exists' method returning true if the Box value satisfies the function" in {
+ Full(1) exists {_ > 0} must beTrue
+ }
+ "define an exists method returning false if the Box value doesn't satisfy the function" in {
+ Full(0) exists {_ > 0} must beFalse
+ }
+ "define a forall method returning true if the Box value satisfies the function" in {
+ Full(1) forall {_ > 0} must beTrue
+ }
+ "define a forall method returning false if the Box value doesn't satisfy the function" in {
+ Full(0) forall {_ > 0} must beFalse
+ }
+ "define a 'filter' method, returning a Full Box if the filter is satisfied" in {
+ (Full(1) filter {(x: Int) => x > 0}) must beEqualTo(Full(1))
+ }
+ "define a 'filter' method, returning Empty if the filter is not satisfied" in {
+ (Full(1) filter {(x: Int) => x == 0}) must beEqualTo(Empty)
+ }
+ "define a 'filterMsg' method, returning a Failure if the filter predicate is not satisfied" in {
+ Full(1).filterMsg("not equal to 0")(_ == 0) must beEqualTo(Failure("not equal to 0", Empty, Empty))
+ }
+ "define a 'foreach' method using its value (to display it for instance)" in {
+ var total = 0
+ Full(1) foreach { total += _ }
+ total === 1
+ }
+ "define a 'map' method to transform its value" in {
+ (Full(1) map { (x: Int) => x.toString }) must beEqualTo(Full("1"))
+ }
+ "define a 'flatMap' method transforming its value in another Box. If the value is transformed in a Full box, the total result is a Full box" in {
+ (Full(1) flatMap { (x: Int) => if (x > 0) Full("full") else Empty }) must beEqualTo(Full("full"))
+ }
+ "define a 'flatMap' method transforming its value in another Box. If the value is transformed in an Empty box, the total result is an Empty box" in {
+ (Full(0) flatMap { (x: Int) => if (x > 0) Full("full") else Empty }) must beEqualTo(Empty)
+ }
+ "define a 'flatten' method if it contains another Box." in {
+ "If the inner box is a Full box, the final result is identical to that box" in {
+ Full(Full(1)).flatten must beEqualTo(Full(1))
+ }
+ "If the inner box is a Failure, the final result is identical to that box" in {
+ Full(Failure("error", Empty, Empty)).flatten must beEqualTo(Failure("error", Empty, Empty))
+ }
+ "If the inner box is an Empty box, the final result is identical to that box" in {
+ Full(Empty).flatten must beEqualTo(Empty)
+ }
+ }
+ "define a 'collect' method that takes a PartialFunction to transform its contents" in {
+ "If the partial-function is defined for the contents of this box, returns a full box containing the result of applying that partial function to this Box's contents" in {
+ (Full("Albus") collect { case "Albus" => "Dumbledore"}) must beEqualTo(Full("Dumbledore"))
+ }
+ "If the partial-function is not defined for the contents of this box, returns Empty" in {
+ (Full("Hermione") collect { case "Albus" => "Dumbledore"}) must beEqualTo(Empty)
+ }
+ }
+ "define a 'transform' method that takes a PartialFunction to transform this box into another box" in {
+ "If the partial-function is defined for this box, returns the result of applying the partial function to it" in {
+ (Full(404) transform {
+ case Full(x: Int) if x != 200 => Failure("Server error")
+ }) must beEqualTo(Failure("Server error"))
+ }
+ "If the partial-function is not defined for this box, returns itself unchanged" in {
+ (Full("Intended Result") transform {
+ case _: EmptyBox => Full("Alternative")
+ case Full("Unexpected Result") => Full("Alternative")
+ }) must beEqualTo(Full("Intended Result"))
+ }
+ }
+ "define a 'flip' method returning Empty" in {
+ (Full(1) flip { _ => "No data found" }) must beEqualTo(Empty)
+ }
+ "define an 'elements' method returning an iterator containing its value" in {
+ Full(1).elements.next() === 1
+ }
+ "define a 'toList' method returning a List containing its value" in {
+ Full(1).toList === List(1)
+ }
+ "define a 'toOption' method returning a Some object containing its value" in {
+ Full(1).toOption must beSome(1)
+ }
+ "return itself if asked for its status with the operator ?~" in {
+ Full(1) ?~ "error" must beEqualTo(Full(1))
+ }
+ "return itself if asked for its status with the operator ?~!" in {
+ Full(1) ?~! "error" must beEqualTo(Full(1))
+ }
+ "define a 'pass' method passing the can to a function and returning itself (alias: $)" in {
+ var empty = false
+ def emptyString(s: Box[String]) = s foreach {(c: String) => empty = c.isEmpty}
+ Full("") $ emptyString _
+ empty must beTrue
+ }
+ "define a 'run' method either returning a default value or applying a user-defined function on it" in {
+ def appendToString(s: String, x: Int) = s + x.toString
+ Full(1).run("string")(appendToString) === "string1"
+ }
+ "define a 'isA' method returning a Full(value) if the value is the instance of a given class" in {
+ Full("s").isA(classOf[String]) must beEqualTo(Full("s"))
+ }
+ "define a 'isA' method returning Empty if the value is not the instance of a given class" in {
+ Full("s").isA(classOf[Double]) must beEqualTo(Empty)
+ }
+ "define a 'asA' method returning a Full(value) if the value is the instance of a given type" in {
+ Full("s").asA[String] must beEqualTo(Full("s"))
+ }
+ "define a 'asA' method returning Empty if the value is not the instance of a given type" in {
+ Full("s").asA[Double] must beEqualTo(Empty)
+ }
+
+ "define a 'asA' method must work with Boolean" in {
+ Full(true).asA[Boolean] must beEqualTo(Full(true))
+ Full(3).asA[Boolean] must beEqualTo(Empty)
+ }
+
+ "define a 'asA' method must work with Character" in {
+ Full('a').asA[Char] must beEqualTo(Full('a'))
+ Full('a').asA[Boolean] must beEqualTo(Empty)
+ }
+
+ "define a 'asA' method must work with Byte" in {
+ Full(3.toByte).asA[Byte] must beEqualTo(Full(3.toByte))
+ Full(3.toByte).asA[Boolean] must beEqualTo(Empty)
+ }
+
+ "define a 'asA' method must work with Double" in {
+ Full(44d).asA[Double] must beEqualTo(Full(44D))
+ Full(44d).asA[Boolean] must beEqualTo(Empty)
+ }
+
+ "define a 'asA' method must work with Float" in {
+ Full(32f).asA[Float] must beEqualTo(Full(32f))
+ Full(33f).asA[Boolean] must beEqualTo(Empty)
+ }
+
+ "define a 'asA' method must work with Integer" in {
+ Full(3).asA[Int] must beEqualTo(Full(3))
+ Full(3).asA[Boolean] must beEqualTo(Empty)
+ }
+
+ "define a 'asA' method must work with Long" in {
+ Full(32L).asA[Long] must beEqualTo(Full(32L))
+ Full(32L).asA[Boolean] must beEqualTo(Empty)
+ }
+
+ "define a 'asA' method must work with Short" in {
+ Full(8.toShort).asA[Short] must beEqualTo(Full(8.toShort))
+ Full(8.toShort).asA[Boolean] must beEqualTo(Empty)
+ }
+
+ "not invoke a call-by-name parameter to openOrThrowException" in {
+ var sideEffect = false
+ def sideEffecting = {
+ sideEffect = true
+ "This shouldn't have been invoked."
+ }
+
+ try {
+ Full("hi mom").openOrThrowException(sideEffecting)
+ } catch {
+ case e: Exception =>
+ }
+
+ sideEffect === false
+ }
+
+ }
+
+ "An Empty Box" should {
+ "beEmpty" in {
+ Empty.isEmpty must beTrue
+ }
+ "not be defined" in {
+ Empty.isDefined must beFalse
+ }
+ "throw an exception if opened" in {
+ {Empty.openOrThrowException("See what happens?, at least we expect it in this case :)"); ()} must throwA[NullPointerException]
+ }
+ "return a default value if opened with openOr" in {
+ Empty.openOr(1) === 1
+ }
+ "return the other Box if or'ed with another Box" in {
+ Empty.or(Full(1)) must beEqualTo(Full(1))
+ }
+ "return itself if filtered with a predicate" in {
+ val empty: Box[Int] = Empty
+ empty.filter {_ > 0} must beEqualTo(Empty)
+ }
+ "define an 'exists' method returning false" in {
+ val empty: Box[Int] = Empty
+ empty exists {_ > 0} must beFalse
+ }
+ "define a 'forall' method returning true" in {
+ val empty: Box[Int] = Empty
+ empty forall {_ > 0} must beTrue
+ }
+ "define a 'filter' method, returning Empty" in {
+ val empty: Box[Int] = Empty
+ (empty filter {(x: Int) => x > 0}) must beEqualTo(Empty)
+ }
+ "define a 'filterMsg' method, returning a Failure" in {
+ Empty.filterMsg("not equal to 0")(_ == 0) must beEqualTo(Failure("not equal to 0", Empty, Empty))
+ }
+ "define a 'foreach' doing nothing" in {
+ var total = 0
+ val empty: Box[Int] = Empty
+ empty foreach {total += _}
+ total === 0
+ }
+ "define a 'map' method returning Empty" in {
+ (Empty map {(x: Any) => x.toString}) must beEqualTo(Empty)
+ }
+ "define a 'flatMap' method returning Empty" in {
+ (Empty flatMap {(x: Int) => Full("full")}) must beEqualTo(Empty)
+ }
+ "define a 'flatten' method returning Empty" in {
+ Empty.flatten must beEqualTo(Empty)
+ }
+ "define a 'collect' method returning Empty" in {
+ (Empty collect { case _ => "Some Value" }) must beEqualTo(Empty)
+ }
+ "define a 'transform' method that takes a PartialFunction to transform this Empty box into another box" in {
+ "If the partial-function is defined for Empty, returns the result of applying the partial function to it" in {
+ (Empty transform {
+ case Failure("error", Empty, Empty) => Full("failure-alternative")
+ case Empty => Full("alternative")
+ }) must beEqualTo(Full("alternative"))
+ }
+ "If the partial-function is not defined for Empty, returns Empty" in {
+ (Empty transform { case Failure("The Phantom Menace", Empty, Empty) => Full("Return Of The Jedi") }) must beEqualTo(Empty)
+ }
+ }
+ "define a 'flip' method returning a Full box" in {
+ (Empty flip {
+ case Empty => "flipped-empty"
+ case _ => "flipped-failure"
+ }) must beEqualTo(Full("flipped-empty"))
+ }
+ "define an 'elements' method returning an empty iterator" in {
+ Empty.elements.hasNext must beFalse
+ }
+ "define a 'toList' method returning Nil" in {
+ Empty.toList must beEmpty
+ }
+ "define a 'toOption' method returning None" in {
+ Empty.toOption must beNone
+ }
+ "return a failure with a message if asked for its status with the operator ?~" in {
+ Empty ?~ "nothing" must beEqualTo(Failure("nothing", Empty, Empty))
+ }
+ "return a failure with a message if asked for its status with the operator ?~!" in {
+ Empty ?~! "nothing" must beEqualTo(Failure("nothing", Empty, Empty))
+ }
+ "define a 'isA' method returning Empty" in {
+ Empty.isA(classOf[Double]) must beEqualTo(Empty)
+ }
+ "define a 'asA' method returning Empty" in {
+ Empty.asA[Double] must beEqualTo(Empty)
+ }
+
+ "invoke a call-by-name parameter to openOrThrowException" in {
+ var sideEffect = false
+ def sideEffecting = {
+ sideEffect = true
+ "This should have been invoked."
+ }
+
+ try {
+ Empty.openOrThrowException(sideEffecting)
+ } catch {
+ case e: Exception =>
+ }
+
+ sideEffect === true
+ }
+ }
+
+ "A Failure is an Empty Box which" can {
+ "return its cause as an exception" in {
+ case class LiftException(m: String) extends Exception
+ Failure("error", Full(new LiftException("broken")), Empty).exception must beEqualTo(Full(new LiftException("broken")))
+ }
+ "return a chained list of causes" in {
+ Failure("error",
+ Full(new Exception("broken")),
+ Full(Failure("nested cause", Empty, Empty))).chain must beEqualTo(Full(Failure("nested cause", Empty, Empty)))
+ }
+ "be converted to a ParamFailure" in {
+ Failure("hi mom") ~> 404 must beEqualTo(ParamFailure("hi mom", Empty, Empty, 404))
+ }
+ }
+
+ "A Failure is an Empty Box which" should {
+ "return itself if mapped, flatMapped or flattened" in {
+ (Failure("error", Empty, Empty) map {(x: Any) => x.toString}) must beEqualTo(Failure("error", Empty, Empty))
+ (Failure("error", Empty, Empty) flatMap {(x: String) => Full(x.toString)}) must beEqualTo(Failure("error", Empty, Empty))
+ Failure("error", Empty, Empty).flatten must beEqualTo(Failure("error", Empty, Empty))
+ }
+ "define a 'collect' method returning itself" in {
+ (Failure("error", Empty, Empty) collect { case _ => "Some Value" }) must beEqualTo(Failure("error", Empty, Empty))
+ }
+ "define a 'transform' method that takes a PartialFunction to transform this Failure into another box" in {
+ "If the partial-function is defined for this Failure, returns the result of applying the partial function to it" in {
+ (Failure("The Phantom Menace") transform {
+ case Failure("The Phantom Menace", Empty, Empty) => Full("Return Of The Jedi")
+ }) must beEqualTo(Full("Return Of The Jedi"))
+
+ (Failure("The Phantom Menace") transform {
+ case Failure("The Phantom Menace", Empty, Empty) => Failure("Clones")
+ case _ => Full("Jedi")
+ }) must beEqualTo(Failure("Clones"))
+ }
+ "If the partial-function is not defined for this Failure, returns itself unchanged" in {
+ (Failure("Clones") transform { case Failure("The Phantom Menace", Empty, Empty) => Full("Jedi") }) must beEqualTo(Failure("Clones"))
+ }
+ }
+ "define a 'flip' method returning a Full box" in {
+ (Failure("error", Empty, Empty) flip {
+ case Empty => "flipped-empty"
+ case _: Failure => "flipped-failure"
+ }) must beEqualTo(Full("flipped-failure"))
+ }
+ "return itself when asked for its status with the operator ?~" in {
+ Failure("error", Empty, Empty) ?~ "nothing" must beEqualTo(Failure("error", Empty, Empty))
+ }
+ "create a new failure with a chained message if asked for its status with the operator ?~!" in {
+ Failure("error", Empty, Empty) ?~! "error2" must beEqualTo(Failure("error2", Empty, Full(Failure("error", Empty, Empty))))
+ }
+ "return false for exist method" in {
+ Failure("error", Empty, Empty) exists {_ => true } must beFalse
+ }
+ "return true for forall method" in {
+ Failure("error", Empty, Empty) forall {_ => false } must beTrue
+ }
+ }
+
+ "A ParamFailure is a failure which" should {
+ "appear in the chain when ~> is invoked on it" in {
+ Failure("Apple") ~> 404 ~> "apple" must beEqualTo(
+ ParamFailure("Apple", Empty, Full(
+ ParamFailure("Apple", Empty, Empty, 404)
+ ), "apple"))
+ }
+ }
+
+ "A Box equals method" should {
+
+ "return true with comparing two identical Box messages" in prop {
+ import org.specs2.execute.Result
+ (c1: Box[Int], c2: Box[Int]) => {
+ ((c1, c2) match {
+ case (Empty, Empty) => c1 must beEqualTo(c2)
+ case (Full(x), Full(y)) => (c1 == c2) === (x == y)
+ case (Failure(m1, e1, l1), Failure(m2, e2, l2)) => (c1 == c2) === ((m1, e1, l1) == (m2, e2, l2))
+ case _ => (c1 != c2) === true
+ }): Result
+ }
+ }
+
+ "return false with comparing one Full and another object" in {
+ (Full(1) != "hello") must beTrue
+ }
+
+ "return false with comparing one Empty and another object" in {
+ (Empty != "hello") must beTrue
+ }
+
+ "return false with comparing one Failure and another object" in {
+ (Failure("", Empty, Empty) != "hello") must beTrue
+ }
+ }
+
+ "A List[Box[T]]" should {
+ "be convertable to a Box[List[T]] when all are Full" in {
+ val someBoxes: List[Box[String]] = List(Full("bacon"), Full("sammich"))
+ val singleBox = someBoxes.toSingleBox("Box failed!")
+
+ singleBox must beEqualTo(Full(List("bacon", "sammich")))
+ }
+
+ "be convertable to a Box[List[T]] when some are Full and some are Empty" in {
+ val someBoxes: List[Box[String]] = List(Full("bacon"), Full("sammich"), Empty)
+ val singleBox = someBoxes.toSingleBox("Box failed!")
+
+ singleBox must beEqualTo(Full(List("bacon", "sammich")))
+ }
+
+ "be convertable to a ParamFailure[Box[List[T]]] when any are Failure" in {
+ val someBoxes: List[Box[String]] = List(Full("bacon"), Full("sammich"), Failure("I HATE BACON"))
+ val singleBox = someBoxes.toSingleBox("This should be in the param failure.")
+
+ singleBox must beLike {
+ case ParamFailure(message, _, _, _) =>
+ message === "This should be in the param failure."
+ }
+ }
+
+ "chain the ParamFailure to the failures in the list when any are Failure" in {
+ val someBoxes: List[Box[String]] = List(Full("bacon"), Failure("I HATE BACON"), Full("sammich"), Failure("MORE BACON FAIL"), Failure("BACON WHY U BACON"))
+
+ val singleBox = someBoxes.toSingleBox("Failure.")
+
+ val expectedChain =
+ Failure("I HATE BACON", Empty,
+ Full(Failure("MORE BACON FAIL", Empty,
+ Full(Failure("BACON WHY U BACON")))))
+
+ singleBox must beLike {
+ case ParamFailure(_, _, chain, _) =>
+ chain must beEqualTo(Full(expectedChain))
+ }
+ }
+ }
+
+ "A Box tryo method" should {
+ "return Full" in {
+ Box.tryo(1) must beEqualTo(Full(1))
+ }
+
+ "return Failure(_, Full(NPE), _) in case of NPE" in {
+ val obj: Object = null
+
+ Box.tryo(obj.toString) must beLike {
+ case Failure(_, Full(ex), _) => ex.getClass === classOf[NullPointerException]
+ }
+ }
+
+ "return Empty in case of NPE and ignore list with NPE" in {
+ val ignore: List[Class[_]] = List(classOf[NullPointerException])
+
+ Box.tryo(ignore)(throw new NullPointerException) must beEqualTo(Empty)
+ }
+
+ "return Failure(_, Full(NPE), _) in case of non empty ignore list without NPE" in {
+ val ignore: List[Class[_]] = List(classOf[IllegalArgumentException])
+
+ Box.tryo(ignore)(throw new NullPointerException) must beLike {
+ case Failure(_, Full(ex), _) => ex.getClass === classOf[NullPointerException]
+ }
+ }
+
+ "not throw NPE in case of nullable ignore list" in {
+ val ignore: List[Class[_]] = null
+
+ Box.tryo(ignore)(throw new IllegalArgumentException) must beLike {
+ case Failure(_, Full(ex), _) => ex.getClass === classOf[IllegalArgumentException]
+ }
+ }
+ }
+}
+
+
+trait BoxGenerator {
+
+ implicit def genThrowable: Arbitrary[Throwable] = Arbitrary[Throwable] {
+ case object UserException extends Throwable
+ const(UserException)
+ }
+
+ implicit def genBox[T](implicit a: Arbitrary[T]): Arbitrary[Box[T]] = Arbitrary[Box[T]] {
+ frequency(
+ (3, const(Empty)),
+ (3, a.arbitrary.map(Full[T])),
+ (1, genFailureBox)
+ )
+ }
+
+ def genFailureBox: Gen[Failure] = for {
+ msgLen <- choose(0, 4)
+ msg <- listOfN(msgLen, alphaChar)
+ exception <- const(Full(new Exception("")))
+ chainLen <- choose(1, 5)
+ chain <- frequency((1, listOfN(chainLen, genFailureBox)), (3, const(Nil)))
+ } yield Failure(msg.mkString, exception, Box(chain.headOption))
+
+}
diff --git a/core/common/src/test/scala-3/net/liftweb/common/ConversionsSpec.scala b/core/common/src/test/scala-3/net/liftweb/common/ConversionsSpec.scala
new file mode 100644
index 000000000..3e4b697b4
--- /dev/null
+++ b/core/common/src/test/scala-3/net/liftweb/common/ConversionsSpec.scala
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2010-2011 WorldWide Conferencing, LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.liftweb
+package common
+
+import scala.xml.{NodeSeq, Text}
+import org.specs2.matcher.XmlMatchers
+import org.specs2.mutable.Specification
+
+import scala.annotation.nowarn
+
+
+/**
+ * System under specification for Conversions.
+ */
+@nowarn("msg=.* NodeSeqFunc in package common.* is deprecate.*")
+class ConversionsSpec extends Specification with XmlMatchers {
+
+ "A StringOrNodeSeq" should {
+
+ "convert from a String" in {
+ val sns: StringOrNodeSeq = "Hello"
+ sns.nodeSeq === Text("Hello")
+ }
+
+ "convert from an Elem" in {
+ val sns: StringOrNodeSeq =
+ sns.nodeSeq must ==/ ()
+ }
+
+ "convert from a Seq[Node]" in {
+ val sns: StringOrNodeSeq = List(, )
+ sns.nodeSeq must ==/ (List(, ) : NodeSeq)
+ }
+ }
+
+ "A StringFunc" should {
+
+ "be created by a String constant" in {
+ val sf: StringFunc = "Foo"
+
+ sf.func() === "Foo"
+ }
+
+ "be created by a String Function" in {
+ val sf: StringFunc = () => "Bar"
+
+ sf.func() === "Bar"
+ }
+
+ "be created by a constant that can be converted to a String" in {
+ implicit def intToString(in: Int): String = in.toString
+ val sf: StringFunc = 55
+
+ sf.func() === "55"
+ }
+
+ "be created by a function that can be converted to a String" in {
+ implicit def intToString(in: Int): String = in.toString
+ val sf: StringFunc = () => 55
+
+ sf.func() === "55"
+ }
+
+ }
+
+ "A NodeSeqFunc" should {
+
+ "be created by a NodeSeq constant" in {
+ val sf: NodeSeqFunc = Foo
+
+ sf.func() must ==/ (Foo)
+ }
+
+ "be created by a NodeSeq Function" in {
+ val sf: NodeSeqFunc = () => Bar
+
+ sf.func() must ==/ (Bar)
+ }
+
+ "be created by a constant that can be converted to a NodeSeq" in {
+ implicit def intToNS(in: Int): NodeSeq = {in}
+ val sf: NodeSeqFunc = 55
+
+ sf.func() must ==/ (55)
+ }
+
+ "be created by a function that can be converted to a NodeSeq" in {
+ implicit def intToNodeSeq(in: Int): NodeSeq = {in}
+ val sf: NodeSeqFunc = () => 55
+
+ sf.func() must ==/ (55)
+ }
+
+ }
+
+}
+
diff --git a/core/common/src/test/scala-3/net/liftweb/common/HListSpec.scala b/core/common/src/test/scala-3/net/liftweb/common/HListSpec.scala
new file mode 100644
index 000000000..72af89f31
--- /dev/null
+++ b/core/common/src/test/scala-3/net/liftweb/common/HListSpec.scala
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2010-2011 WorldWide Conferencing, LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.liftweb
+package common
+
+import org.specs2.mutable.Specification
+
+
+/**
+ * System under specification for Heterogeneous List.
+ */
+class HListSpec extends Specification {
+
+ "An HList" should {
+
+ "get the types right" in {
+ import HLists._
+
+ val x = 1 :+: "Foo" :+: HNil
+
+ val head: Int = x.head
+ val head2: String = x.tail.head
+
+ x.head === 1
+ x.tail.head === "Foo"
+ }
+
+ "properly report its length" in {
+ import HLists._
+
+ val x = 1 :+: "Foo" :+: HNil
+
+ HNil.length === 0
+ x.length === 2
+ ("Bam" :+: x).length === 3
+ }
+ }
+
+ "A combinable box" should {
+
+ "have a box built with a failure result in a failure" in {
+ import CombinableBox._
+
+ val x = Full("a") :&: Full(1) :&: Empty
+
+ // result in a failure
+ x match {
+ case Left(_) => success
+ case _ => failure
+ }
+ }
+
+ "be able to build a box with all the Full elements matching" in {
+ import CombinableBox._
+ import HLists._
+
+ val x = Full("a") :&: Full(1) :&: Full(List(1,2,3))
+
+ // result in a failure
+ x match {
+ case Right(a :+: one :+: lst :+:HNil) => {
+ // val a2: Int = a fails... not type safe
+
+ val as: String = a
+ val onei: Int = one
+ val lstl: List[Int] = lst
+
+ success
+ }
+
+ case Right(_) => failure
+ case Left(_) => failure
+ }
+ }
+
+ "be usable in for comprehension" in {
+ import CombinableBox._
+ import HLists._
+
+ val res = for {
+ a :+: one :+: lst :+: _ <-
+ (Full("a") ?~ "Yak" :&: Full(1) :&: Full(List(1,2,3))) ?~! "Dude"
+ } yield a.length * one * lst.foldLeft(1)(_ * _)
+
+ res must beEqualTo(Full(6))
+ }
+ }
+
+}
+
diff --git a/core/common/src/test/scala-3/net/liftweb/common/LoggingSpec.scala b/core/common/src/test/scala-3/net/liftweb/common/LoggingSpec.scala
new file mode 100644
index 000000000..ca4e7bd0c
--- /dev/null
+++ b/core/common/src/test/scala-3/net/liftweb/common/LoggingSpec.scala
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2010-2011 WorldWide Conferencing, LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.liftweb
+package common
+
+import org.specs2.mutable.Specification
+
+
+/**
+ * System under specification for Logging.
+ *
+ * Tests rely on logback being in the classpath, so no configuration should be necessary.
+ */
+class LoggingSpec extends Specification {
+ "Logging" can {
+ "be mixed directly into object" in {
+ object MyObj extends Logger {
+ info("direct Hello")
+ val x = 2
+ }
+ MyObj.x === 2
+
+ (new MyTopClass).x === 1
+ MyTopObj.x ===1
+ }
+
+ "be nested in object" in {
+ object MyObj extends Loggable {
+ logger.info("nested Hello")
+ val x = 2
+ }
+
+ MyObj.x === 2
+
+ }
+
+ "create named loggers" in {
+ val logger = Logger("MyLogger")
+
+ logger.info("Logged with my named logger")
+ success
+ }
+
+ "log static MDC values" in {
+ val logger = Logger("StaticMDC")
+
+ logger.info("Logged with no MDC")
+ MDC.put("mdc1" -> (1,2))
+ logger.info("Logged with mdc1=(1,2)")
+ MDC.put("mdc2" -> "yy")
+ logger.info("Logged with mdc1=(1,2), mdc2=yy")
+ MDC.put("mdc1" -> 99)
+ logger.info("Logged with mdc1=99, mdc2=yy")
+ MDC.remove("mdc1")
+ logger.info("Logged with mdc2=yy")
+ MDC.clear()
+ logger.info("Logged with no MDC")
+ success
+ }
+
+ "save MDC context with logWith" in {
+ val logger = Logger("logWith")
+
+ logger.info("Logged with no MDC")
+ MDC.put("mdc1" -> (1,2), "mdc2" -> "yy")
+ logger.info("Logged with mdc1=(1,2), mdc2=yy")
+ Logger.logWith("mdc2" -> "xx") {
+ logger.info("Logged with mdc1=(1,2), mdc2=xx")
+ Logger.logWith("mdc1" -> 99) {
+ logger.info("Logged with mdc1=99, mdc2=xx")
+ }
+ logger.info("Logged with mdc1=(1,2), mdc2=xx")
+ }
+ logger.info("Logged with mdc1=(1,2), mdc2=yy")
+ MDC.clear()
+ logger.info("No MDC values")
+ success
+ }
+ "trace function results" in {
+ object MyObj extends Logger {
+ val l = 1 to 10
+ info("Starting test")
+ trace("result",l.foldLeft(0)(trace("lhs",_) + trace("rhs",_))) === l.foldLeft(0)(_+_)
+ val x = 1
+ }
+ MyObj
+ success
+ }
+
+ "be used in different levels and yield different loggers" in {
+ class First {
+ First.info("In first")
+ }
+ object First extends Logger
+
+ trait Second {
+ private val logger = Logger(classOf[Second])
+ logger.info("In second")
+ }
+
+ class C extends First with Second with Logger {
+ info("In C")
+ val x = 2
+ }
+ (new C).x === 2
+ }
+ }
+}
+
+
+class MyTopClass extends Logger {
+ val x=1
+ debug("Top level class logging")
+}
+
+
+object MyTopObj extends Logger {
+ val x=1
+ debug("Top level object logging")
+}
+
diff --git a/core/common/src/test/scala-3/net/liftweb/common/LruMapSpec.scala b/core/common/src/test/scala-3/net/liftweb/common/LruMapSpec.scala
new file mode 100644
index 000000000..c0878d005
--- /dev/null
+++ b/core/common/src/test/scala-3/net/liftweb/common/LruMapSpec.scala
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2010-2011 WorldWide Conferencing, LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.liftweb
+package common
+
+import org.specs2.mutable.Specification
+
+
+/**
+ * Systems under specification for LRU Map.
+ */
+class LruMapSpec extends Specification {
+
+ "An LRU Map" should {
+
+ "never grow beyond the given size" in {
+ val lru = new LRUMap[Int, Int](10)
+ for (i <- 1 to 20) lru(i) = i
+
+ lru.size === 10
+ }
+
+ "have the last N elements (where N is the initial MaxSize)" in {
+ val lru = new LRUMap[Int, Int](10)
+ for (i <- 1 to 20) lru(i) = i
+
+ lru.size === 10
+ (11 to 20).forall(i => lru(i) == i) must beTrue
+ }
+
+ "expire elements to func" in {
+ var expCnt = 0
+ val lru = new LRUMap[Int, Int](10, Empty, (k, v) => {expCnt += 1; (k == v) && (k > 0) && (v < 11)})
+ for (i <- 1 to 20) lru(i) = i
+
+ lru.size === 10
+ expCnt === 10
+ (11 to 20).forall(i => lru(i) == i) must beTrue
+ }
+
+ "not expire the recently accessed elements" in {
+ var expCnt = 0
+ val lru = new LRUMap[Int, Int](10, Empty, (k, v) => {expCnt += 1; (k == v) && (k > 0)})
+ for (i <- 1 to 20) {
+ for (q <- 1 to 10) lru.get(q)
+ lru(i) = i
+ }
+
+ lru.size === 10
+ for (i <- 2 to 10) lru(i) === i
+ lru(20) === 20
+ }
+
+ }
+
+}
+
diff --git a/core/util/src/test/scala/net/liftweb/util/BasicTypesHelpersSpec.scala b/core/util/src/test/scala-2.13/net/liftweb/util/BasicTypesHelpersSpec.scala
similarity index 100%
rename from core/util/src/test/scala/net/liftweb/util/BasicTypesHelpersSpec.scala
rename to core/util/src/test/scala-2.13/net/liftweb/util/BasicTypesHelpersSpec.scala
diff --git a/core/util/src/test/scala/net/liftweb/util/BundleBuilderSpec.scala b/core/util/src/test/scala-2.13/net/liftweb/util/BundleBuilderSpec.scala
similarity index 100%
rename from core/util/src/test/scala/net/liftweb/util/BundleBuilderSpec.scala
rename to core/util/src/test/scala-2.13/net/liftweb/util/BundleBuilderSpec.scala
diff --git a/core/util/src/test/scala/net/liftweb/util/CanResolveAsyncSpec.scala b/core/util/src/test/scala-2.13/net/liftweb/util/CanResolveAsyncSpec.scala
similarity index 100%
rename from core/util/src/test/scala/net/liftweb/util/CanResolveAsyncSpec.scala
rename to core/util/src/test/scala-2.13/net/liftweb/util/CanResolveAsyncSpec.scala
diff --git a/core/util/src/test/scala/net/liftweb/util/ClassHelpersSpec.scala b/core/util/src/test/scala-2.13/net/liftweb/util/ClassHelpersSpec.scala
similarity index 100%
rename from core/util/src/test/scala/net/liftweb/util/ClassHelpersSpec.scala
rename to core/util/src/test/scala-2.13/net/liftweb/util/ClassHelpersSpec.scala
diff --git a/core/util/src/test/scala/net/liftweb/util/CombParserHelpersSpec.scala b/core/util/src/test/scala-2.13/net/liftweb/util/CombParserHelpersSpec.scala
similarity index 100%
rename from core/util/src/test/scala/net/liftweb/util/CombParserHelpersSpec.scala
rename to core/util/src/test/scala-2.13/net/liftweb/util/CombParserHelpersSpec.scala
diff --git a/core/util/src/test/scala/net/liftweb/util/ConnectionIdentifierSpec.scala b/core/util/src/test/scala-2.13/net/liftweb/util/ConnectionIdentifierSpec.scala
similarity index 100%
rename from core/util/src/test/scala/net/liftweb/util/ConnectionIdentifierSpec.scala
rename to core/util/src/test/scala-2.13/net/liftweb/util/ConnectionIdentifierSpec.scala
diff --git a/core/util/src/test/scala/net/liftweb/util/ControlHelpersSpec.scala b/core/util/src/test/scala-2.13/net/liftweb/util/ControlHelpersSpec.scala
similarity index 100%
rename from core/util/src/test/scala/net/liftweb/util/ControlHelpersSpec.scala
rename to core/util/src/test/scala-2.13/net/liftweb/util/ControlHelpersSpec.scala
diff --git a/core/util/src/test/scala/net/liftweb/util/CssHelpersSpec.scala b/core/util/src/test/scala-2.13/net/liftweb/util/CssHelpersSpec.scala
similarity index 100%
rename from core/util/src/test/scala/net/liftweb/util/CssHelpersSpec.scala
rename to core/util/src/test/scala-2.13/net/liftweb/util/CssHelpersSpec.scala
diff --git a/core/util/src/test/scala/net/liftweb/util/CssSelectorSpec.scala b/core/util/src/test/scala-2.13/net/liftweb/util/CssSelectorSpec.scala
similarity index 100%
rename from core/util/src/test/scala/net/liftweb/util/CssSelectorSpec.scala
rename to core/util/src/test/scala-2.13/net/liftweb/util/CssSelectorSpec.scala
diff --git a/core/util/src/test/scala/net/liftweb/util/CurrencyZoneSpec.scala b/core/util/src/test/scala-2.13/net/liftweb/util/CurrencyZoneSpec.scala
similarity index 100%
rename from core/util/src/test/scala/net/liftweb/util/CurrencyZoneSpec.scala
rename to core/util/src/test/scala-2.13/net/liftweb/util/CurrencyZoneSpec.scala
diff --git a/core/util/src/test/scala/net/liftweb/util/Html5ParserSpec.scala b/core/util/src/test/scala-2.13/net/liftweb/util/Html5ParserSpec.scala
similarity index 100%
rename from core/util/src/test/scala/net/liftweb/util/Html5ParserSpec.scala
rename to core/util/src/test/scala-2.13/net/liftweb/util/Html5ParserSpec.scala
diff --git a/core/util/src/test/scala/net/liftweb/util/HtmlHelpersSpec.scala b/core/util/src/test/scala-2.13/net/liftweb/util/HtmlHelpersSpec.scala
similarity index 100%
rename from core/util/src/test/scala/net/liftweb/util/HtmlHelpersSpec.scala
rename to core/util/src/test/scala-2.13/net/liftweb/util/HtmlHelpersSpec.scala
diff --git a/core/util/src/test/scala/net/liftweb/util/HttpHelpersSpec.scala b/core/util/src/test/scala-2.13/net/liftweb/util/HttpHelpersSpec.scala
similarity index 100%
rename from core/util/src/test/scala/net/liftweb/util/HttpHelpersSpec.scala
rename to core/util/src/test/scala-2.13/net/liftweb/util/HttpHelpersSpec.scala
diff --git a/core/util/src/test/scala/net/liftweb/util/IoHelpersSpec.scala b/core/util/src/test/scala-2.13/net/liftweb/util/IoHelpersSpec.scala
similarity index 100%
rename from core/util/src/test/scala/net/liftweb/util/IoHelpersSpec.scala
rename to core/util/src/test/scala-2.13/net/liftweb/util/IoHelpersSpec.scala
diff --git a/core/util/src/test/scala/net/liftweb/util/JsonCommandSpec.scala b/core/util/src/test/scala-2.13/net/liftweb/util/JsonCommandSpec.scala
similarity index 100%
rename from core/util/src/test/scala/net/liftweb/util/JsonCommandSpec.scala
rename to core/util/src/test/scala-2.13/net/liftweb/util/JsonCommandSpec.scala
diff --git a/core/util/src/test/scala/net/liftweb/util/ListHelpersSpec.scala b/core/util/src/test/scala-2.13/net/liftweb/util/ListHelpersSpec.scala
similarity index 100%
rename from core/util/src/test/scala/net/liftweb/util/ListHelpersSpec.scala
rename to core/util/src/test/scala-2.13/net/liftweb/util/ListHelpersSpec.scala
diff --git a/core/util/src/test/scala/net/liftweb/util/PCDataXmlParserSpec.scala b/core/util/src/test/scala-2.13/net/liftweb/util/PCDataXmlParserSpec.scala
similarity index 100%
rename from core/util/src/test/scala/net/liftweb/util/PCDataXmlParserSpec.scala
rename to core/util/src/test/scala-2.13/net/liftweb/util/PCDataXmlParserSpec.scala
diff --git a/core/util/src/test/scala/net/liftweb/util/PropsSpec.scala b/core/util/src/test/scala-2.13/net/liftweb/util/PropsSpec.scala
similarity index 100%
rename from core/util/src/test/scala/net/liftweb/util/PropsSpec.scala
rename to core/util/src/test/scala-2.13/net/liftweb/util/PropsSpec.scala
diff --git a/core/util/src/test/scala/net/liftweb/util/ScheduleSpec.scala b/core/util/src/test/scala-2.13/net/liftweb/util/ScheduleSpec.scala
similarity index 100%
rename from core/util/src/test/scala/net/liftweb/util/ScheduleSpec.scala
rename to core/util/src/test/scala-2.13/net/liftweb/util/ScheduleSpec.scala
diff --git a/core/util/src/test/scala/net/liftweb/util/SecurityHelpersSpec.scala b/core/util/src/test/scala-2.13/net/liftweb/util/SecurityHelpersSpec.scala
similarity index 100%
rename from core/util/src/test/scala/net/liftweb/util/SecurityHelpersSpec.scala
rename to core/util/src/test/scala-2.13/net/liftweb/util/SecurityHelpersSpec.scala
diff --git a/core/util/src/test/scala/net/liftweb/util/SoftReferenceCacheSpec.scala b/core/util/src/test/scala-2.13/net/liftweb/util/SoftReferenceCacheSpec.scala
similarity index 100%
rename from core/util/src/test/scala/net/liftweb/util/SoftReferenceCacheSpec.scala
rename to core/util/src/test/scala-2.13/net/liftweb/util/SoftReferenceCacheSpec.scala
diff --git a/core/util/src/test/scala/net/liftweb/util/StringHelpersSpec.scala b/core/util/src/test/scala-2.13/net/liftweb/util/StringHelpersSpec.scala
similarity index 100%
rename from core/util/src/test/scala/net/liftweb/util/StringHelpersSpec.scala
rename to core/util/src/test/scala-2.13/net/liftweb/util/StringHelpersSpec.scala
diff --git a/core/util/src/test/scala/net/liftweb/util/TimeHelpersSpec.scala b/core/util/src/test/scala-2.13/net/liftweb/util/TimeHelpersSpec.scala
similarity index 100%
rename from core/util/src/test/scala/net/liftweb/util/TimeHelpersSpec.scala
rename to core/util/src/test/scala-2.13/net/liftweb/util/TimeHelpersSpec.scala
diff --git a/core/util/src/test/scala/net/liftweb/util/ToHeadSpec.scala b/core/util/src/test/scala-2.13/net/liftweb/util/ToHeadSpec.scala
similarity index 100%
rename from core/util/src/test/scala/net/liftweb/util/ToHeadSpec.scala
rename to core/util/src/test/scala-2.13/net/liftweb/util/ToHeadSpec.scala
diff --git a/core/util/src/test/scala/net/liftweb/util/VCardParserSpec.scala b/core/util/src/test/scala-2.13/net/liftweb/util/VCardParserSpec.scala
similarity index 100%
rename from core/util/src/test/scala/net/liftweb/util/VCardParserSpec.scala
rename to core/util/src/test/scala-2.13/net/liftweb/util/VCardParserSpec.scala
diff --git a/core/util/src/test/scala/net/liftweb/util/XmlParserSpec.scala b/core/util/src/test/scala-2.13/net/liftweb/util/XmlParserSpec.scala
similarity index 100%
rename from core/util/src/test/scala/net/liftweb/util/XmlParserSpec.scala
rename to core/util/src/test/scala-2.13/net/liftweb/util/XmlParserSpec.scala
diff --git a/core/util/src/test/scala-3/net/liftweb/util/BasicTypesHelpersSpec.scala b/core/util/src/test/scala-3/net/liftweb/util/BasicTypesHelpersSpec.scala
new file mode 100644
index 000000000..ba201064a
--- /dev/null
+++ b/core/util/src/test/scala-3/net/liftweb/util/BasicTypesHelpersSpec.scala
@@ -0,0 +1,193 @@
+/*
+ * Copyright 2006-2011 WorldWide Conferencing, LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.liftweb
+package util
+
+import java.io.ByteArrayInputStream
+
+import org.specs2.mutable.Specification
+import org.specs2.matcher.DataTables
+
+import common._
+import BasicTypesHelpers._
+
+
+/**
+ * Systems under specification for BasicTypesHelpers.
+ */
+class BasicTypesHelpersSpec extends Specification with DataTables {
+ "BasicTypesHelpers Specification".title
+
+ "Basic types helpers" should {
+
+ "be lazy" in {
+ (false.?[Int]({throw new Exception("Bummer")}).|(3)) === 3
+ (true.?[Int](3).|({throw new Exception("Bummer")})) === 3
+ }
+
+ "provide a ternary operator: (condition) ? A | B" in {
+ ((1 == 2) ? "a" | "b") === "b"
+ }
+
+ "provide a ?> operator to add an element to a list if an expression is true" in {
+ (1 == 1) ?> "a" ::: List("b") === List("a", "b")
+ (1 == 2) ?> "a" ::: List("b") === List("b")
+ }
+ val failure = Failure(null, null, null)
+ "have a toBoolean method converting any object to a reasonable Boolean value" in {
+ toBoolean(null) === false
+ "object value" ||"boolean value" |
+ (0: Any) !!false |
+ 1 !!true |
+ true !!true |
+ false !!false |
+ "" !!false |
+ "string" !!false |
+ "t" !!true |
+ "total" !!false |
+ "T" !!true |
+ "This" !!false |
+ "0" !!false |
+ "on" !!true |
+ None !!false |
+ Some("t") !!true |
+ Empty !!false |
+ Full("t") !!true |
+ failure !!false |
+ List("t", "f") !!true |> {
+ (o: Any, result: Boolean) => toBoolean(o) === result
+ }
+ }
+
+ "have a AsBoolean extractor converting any object to a reasonable Boolean value" in {
+ "object value" ||"boolean value" |>
+ "t" !!Some(true) |
+ "" !!None |
+ "string" !!None |
+ "total" !!None |
+ "T" !!Some(true) |
+ "This" !!None |
+ "0" !!Some(false) | {
+ (o: String, result: Option[Boolean]) => AsBoolean.unapply(o) === result
+ }
+ }
+
+ "have an AsInt extractor converting any String to a reasonable Int value" in {
+ "object value" ||"int value" |>
+ "3" !!Some(3) |
+ "n" !!None | {
+ (o: String, result: Option[Int]) => AsInt.unapply(o) === result
+ }
+ }
+
+ "have an AsLong extractor converting any String to a reasonable Long value" in {
+ "object value" ||"long value" |>
+ "3" !!Some(3L) |
+ "n" !!None | {
+ (o: String, result: Option[Long]) => AsLong.unapply(o) === result
+ }
+ }
+
+ "have a toInt method converting any object to a reasonable Int value" in {
+ def date(t: Int) = new _root_.java.util.Date(t)
+ toInt(null) === 0
+ "object value" ||"int value" |>
+ 1 !!1 |
+ 1L !!1 |
+ List(1, 2) !!1 |
+ Some(1) !!1 |
+ Full(1) !!1 |
+ None !!0 |
+ Empty !!0 |
+ failure !!0 |
+ "3" !!3 |
+ "n" !!0 |
+ date(3000) !!3 | {
+ (o: Any, result: Int) => toInt(o) === result
+ }
+ }
+
+ "have a toLong method converting any object to a reasonable Long value" in {
+ def date(t: Int) = new _root_.java.util.Date(t)
+ toLong(null) === 0L
+ "object value" ||"long value" |>
+ 1 !!1L |
+ 1L !!1L |
+ List(1, 2) !!1L |
+ Some(1) !!1L |
+ Full(1) !!1L |
+ None !!0L |
+ Empty !!0L |
+ failure !!0L |
+ "3" !!3L |
+ "n" !!0L |
+ date(3000) !!3000L | {
+ (o: Any, result: Long) => toLong(o) === result
+ }
+ }
+
+ "have a toByteArrayInputStream reading an InputStream to a ByteArrayInputStream" in {
+ var array: Array[Byte] = Array(12, 14)
+ val input = new ByteArrayInputStream(array)
+ val result = toByteArrayInputStream(input)
+ result.read === 12
+ result.read === 14
+ }
+ "have a isEq method comparing 2 Byte arrays and returning true if they contain the same elements" in {
+ var a: Array[Byte] = Array(12, 14)
+ var b: Array[Byte] = Array(12, 14)
+ var c: Array[Byte] = Array(12, 13)
+ isEq(a, b) must beTrue
+ isEq(a, c) must beFalse
+ }
+ "have a notEq method comparing 2 Byte arrays and returning true if they don't contain the same elements" in {
+ var a: Array[Byte] = Array(12, 14)
+ var b: Array[Byte] = Array(12, 13)
+ BasicTypesHelpers.notEq(a, b) must beTrue
+ }
+ }
+
+ "PartialFunction guard" should {
+
+ "put a guard around a partial function" in {
+ val pf1: PartialFunction[String, Unit] = {
+ case s if s.startsWith("s") =>
+ }
+
+ val pf2: PartialFunction[String, Boolean] = {
+ case "snipe" => true
+ case "bipe" => false
+ }
+
+ val pf3 = pf1.guard(pf2)
+ val pf4: PartialFunction[String, Boolean] = pf1.guard(pf3)
+
+ pf3.isDefinedAt("bipe") === false
+ pf3.isDefinedAt("snipe") === true
+ }
+ }
+
+ "AvoidTypeErasure implicit value" should {
+ "be in scope" in {
+ def f(i:Int)(implicit d: AvoidTypeErasureIssues1) = i+1
+
+ f(2) === 3
+ }
+ }
+
+}
+
diff --git a/core/util/src/test/scala-3/net/liftweb/util/BundleBuilderSpec.scala b/core/util/src/test/scala-3/net/liftweb/util/BundleBuilderSpec.scala
new file mode 100644
index 000000000..4d212db3d
--- /dev/null
+++ b/core/util/src/test/scala-3/net/liftweb/util/BundleBuilderSpec.scala
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2010-2011 WorldWide Conferencing, LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.liftweb
+package util
+
+import java.util.Locale
+
+import scala.xml.NodeSeq
+
+import org.specs2.matcher.XmlMatchers
+import org.specs2.mutable.Specification
+
+
+/**
+ * Systems under specification for BundleBuilder.
+ */
+class BundleBuilderSpec extends Specification with XmlMatchers {
+ "BundleBuilder Specification".title
+
+ "BundleBuilder" should {
+ "Build a Bundle" in {
+ val b = BundleBuilder.convert(
+
Dog
+
Chien
+
hi
+
, Locale.US).openOrThrowException("Test")
+
+ b.getObject("dog") must beEqualTo("Dog")
+ b.getObject("cat").asInstanceOf[NodeSeq] must ==/ (
hi
)
+ }
+
+ "Build a Bundle must support default" in {
+ val b = BundleBuilder.convert(
+
Dog
+
Chien
+
hi
+
, Locale.US).openOrThrowException("Test")
+
+ b.getObject("dog") must beEqualTo("Chien")
+ b.getObject("cat").asInstanceOf[NodeSeq] must ==/ (
hi
)
+ }
+
+ }
+}
+
diff --git a/core/util/src/test/scala-3/net/liftweb/util/CanResolveAsyncSpec.scala b/core/util/src/test/scala-3/net/liftweb/util/CanResolveAsyncSpec.scala
new file mode 100644
index 000000000..08e85b305
--- /dev/null
+++ b/core/util/src/test/scala-3/net/liftweb/util/CanResolveAsyncSpec.scala
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2015 WorldWide Conferencing, LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.liftweb
+package util
+
+import scala.concurrent.{Future, Promise}
+import scala.concurrent.ExecutionContext.Implicits.global
+
+import org.specs2.mutable.Specification
+
+import actor.LAFuture
+
+object CanResolveAsyncSpec extends Specification {
+ "CanResolveAsync" should {
+ "resolve Scala Futures" in {
+ val myPromise = Promise[String]()
+
+ val resolver = implicitly[CanResolveAsync[Future[String], String]]
+
+ val receivedResolution = new LAFuture[String]
+ resolver.resolveAsync(myPromise.future, receivedResolution.satisfy _)
+
+ myPromise.success("All done!")
+
+ receivedResolution.get === "All done!"
+ }
+
+ "resolve LAFutures" in {
+ val myFuture = new LAFuture[String]
+
+ val resolver = implicitly[CanResolveAsync[LAFuture[String], String]]
+
+ val receivedResolution = new LAFuture[String]
+ resolver.resolveAsync(myFuture, receivedResolution.satisfy _)
+
+ myFuture.satisfy("Got it!")
+
+ receivedResolution.get === "Got it!"
+ }
+ }
+}
diff --git a/core/util/src/test/scala-3/net/liftweb/util/ClassHelpersSpec.scala b/core/util/src/test/scala-3/net/liftweb/util/ClassHelpersSpec.scala
new file mode 100644
index 000000000..2dd993236
--- /dev/null
+++ b/core/util/src/test/scala-3/net/liftweb/util/ClassHelpersSpec.scala
@@ -0,0 +1,185 @@
+/*
+ * Copyright 2006-2011 WorldWide Conferencing, LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.liftweb
+package util
+
+import org.specs2.mutable.Specification
+
+import common._
+import ClassHelpers._
+
+
+/**
+ * Systems under specification for ClassHelpers.
+ */
+class ClassHelpersSpec extends Specification {
+ "ClassHelpers Specification".title
+
+ "The findType function" should {
+ "return a Full can with the found class when given the type, the name, and a list of packages to conform to" in {
+ findType[java.util.List[Object]]("ArrayList", List("java.util")) must beEqualTo(Full(classOf[java.util.ArrayList[Object]]))
+ }
+ "return an Empty can if the class cannot be coerced to the expected type" in {
+ findType[String]("ClassHelpers", List("net.liftweb.util")) must beEqualTo(Empty)
+ }
+ }
+ "the findClass function" should {
+ "return a Full can with the found class when given the name and package" in {
+ findClass("ClassHelpers", List("net.liftweb.util")) must beEqualTo(Full(classOf[ClassHelpers]))
+ }
+ "return a Full can with the found class when given the name and package, with an underscored name instead of CamelCased" in {
+ findClass("class_helpers", List("net.liftweb.util")) must beEqualTo(Full(classOf[ClassHelpers]))
+ }
+ "return a Full can with the found class when given the name and a list of packages" in {
+ findClass("ClassHelpers", List("net.liftweb.util", "java.util")) must beEqualTo(Full(classOf[ClassHelpers]))
+ findClass("ArrayList", List("net.liftweb.util", "java.util")) must beEqualTo(Full(classOf[java.util.ArrayList[_]]))
+ }
+ "return a Full can with the found class when given the name, a list of packages and a target type to conform to" in {
+ findClass("ArrayList", List("java.util"), classOf[java.util.List[Object]]) must beEqualTo(Full(classOf[java.util.ArrayList[Object]]))
+ }
+ "return an Empty can if no class is found given a name and package" in {
+ findClass("ClassHelpers", List("net.liftweb.nothere")) must beEqualTo(Empty)
+ }
+ "return an Empty can if the class cannot be coerced to the expected type" in {
+ findClass("ClassHelpers", List("net.liftweb.util"), classOf[String]) must beEqualTo(Empty)
+ }
+ }
+
+ "The findClass function" should {
+ "return a Full can with the found class when given a list of names and corresponding packages" in {
+ findClass(List(("wrong name", List("net.liftweb.util", "other.package")),
+ ("ClassHelpers", List("net.liftweb.util", "other.package")))) must beEqualTo(Full(classOf[ClassHelpers]))
+ }
+ "use a list of modifiers functions to try to modify the original name in order to find the class" in {
+ findClass("classHelpers", List("net.liftweb.util"), List((n: String) => n.capitalize)) must beEqualTo(Full(classOf[ClassHelpers]))
+ }
+ }
+
+ "The callableMethod_? function" should {
+ "return true if the method is public and has no parameters" in {
+ val publicParameterLess = classOf[String].getMethod("length")
+ callableMethod_?(publicParameterLess) must beTrue
+ }
+ "return false if the method is public and has parameters" in {
+ val publicWithParameters = classOf[String].getMethod("indexOf", classOf[String])
+ callableMethod_?(publicWithParameters) must beFalse
+ }
+ "return false if the method is private" in {
+ val privateMethod = classOf[java.util.ArrayList[Object]].getDeclaredMethod("readObject", classOf[java.io.ObjectInputStream])
+ callableMethod_?(privateMethod) must beFalse
+ }
+ "return false if the method is null" in {
+ callableMethod_?(null) must beFalse
+ }
+ }
+
+ "The containsClass function" should {
+ "return false if the list to match is null or empty" in {
+ containsClass(classOf[String], null) must beFalse
+ containsClass(classOf[String], Nil) must beFalse
+ }
+ "return false if the list to match doesn't contain any class assignable by the tested class" in {
+ containsClass(classOf[String], List(classOf[Float], classOf[java.lang.Integer])) must beFalse
+ }
+ }
+
+ "The classHasControllerMethod function" should {
+ "return true if the class has 'name' as a callable method" in {
+ classHasControllerMethod(classOf[String], "length") must beTrue
+ }
+ "return false if the class doesn't have 'name' as a method" in {
+ classHasControllerMethod(classOf[String], "isNotEmpty") must beFalse
+ }
+ "return false if the class has a method but it is not callable" in {
+ classHasControllerMethod(classOf[java.util.ArrayList[Object]], "readObject") must beFalse
+ }
+ "return false if the class is null" in {
+ classHasControllerMethod(null, "readObject") must beFalse
+ }
+ }
+
+ "The invokeControllerMethod function" should {
+ "return the result of calling the method on a new instance of the class" in {
+ invokeControllerMethod(classOf[String], "length") must beEqualTo(0)
+ }
+ "throw an exception when the method is not callable" in {
+ invokeControllerMethod(classOf[String], "isNotEmpty") must throwA[NoSuchMethodException]
+ }
+ "throw an exception if the class is null" in {
+ invokeControllerMethod(null, "length") must throwA[NullPointerException]
+ }
+ }
+
+ "The invokeMethod function" should {
+ "return a Failure if the class is null" in {
+ invokeMethod(null, "", "length") must beLike { case Failure(_, _, _) => 1 === 1 }
+ }
+ "return a Failure if the instance is null" in {
+ invokeMethod(classOf[String], null, "length") must beLike { case Failure(_, _, _) => 1 === 1 }
+ }
+ "return a Failure if the method name is null" in {
+ invokeMethod(classOf[String], "", null) must beLike { case Failure(_, _, _) => 1 === 1 }
+ }
+ "return a Failure if the method doesnt exist on the class" in {
+ invokeMethod(classOf[String], "", "isNotEmpty") must beLike { case Failure(_, _, _) => 1 === 1 }
+ }
+ "return a Full can with the result if the method exist on the class" in {
+ invokeMethod(classOf[String], "", "length") must beEqualTo(Full(0))
+ }
+ "return a Full can with the result if the method is an existing static method on the class" in {
+ invokeMethod(classOf[java.util.Calendar], null, "getInstance").isEmpty === false
+ }
+ "throw an exception if the method throws an exception" in {
+ class SpecificException extends Exception
+ class TestSnippet { def throwException = throw new SpecificException }
+ val testSnippet = new TestSnippet
+ invokeMethod(testSnippet.getClass, testSnippet, "throwException") must throwA[SpecificException]
+ }
+ }
+
+ "The invokeMethod function" can {
+ "call a method with its parameters" in {
+ invokeMethod(classOf[String], "", "valueOf", Array("1")) must beEqualTo(Full("1"))
+ }
+ "call a method with its parameters and parameter types" in {
+ invokeMethod(classOf[String], "", "valueOf", Array("c"), Array(classOf[String])) must beEqualTo(Full("c"))
+ }
+ }
+
+ "The instantiate function" should {
+ "return a full can if a class can be instantiated with a new instance" in {
+ instantiate(classOf[String]) must beEqualTo(Full(""))
+ }
+ "return a failure if a class can not be instantiated with a new instance" in {
+ instantiate(classOf[java.util.Calendar]) must beLike { case Failure(_, _, _) => 1 === 1 }
+ }
+ }
+
+ "The createInvoker function" should {
+ "return Empty if the instance is null" in {
+ createInvoker("length", null) must beEqualTo(Empty)
+ }
+ "return a Full Box with the function from Unit to a Box containing the result of the method to invoke" in {
+ createInvoker("length", "").openOrThrowException("Test").apply() must beEqualTo(Full(0))
+ }
+ "The invoker function will throw the cause exception if the method can't be called" in {
+ (() => createInvoker("get", "").openOrThrowException("Test").apply())() must throwA[Exception]
+ }
+ }
+
+}
+
diff --git a/core/util/src/test/scala-3/net/liftweb/util/CombParserHelpersSpec.scala b/core/util/src/test/scala-3/net/liftweb/util/CombParserHelpersSpec.scala
new file mode 100644
index 000000000..df56cde1a
--- /dev/null
+++ b/core/util/src/test/scala-3/net/liftweb/util/CombParserHelpersSpec.scala
@@ -0,0 +1,181 @@
+/*
+ * Copyright 2007-2011 WorldWide Conferencing, LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.liftweb
+package util
+
+import scala.util.parsing.combinator.Parsers
+
+import org.specs2.mutable.Specification
+import org.specs2.ScalaCheck
+import org.scalacheck.{Arbitrary, Gen, Prop}
+import Gen._
+import Prop._
+
+
+class CombParserHelpersSpec extends Specification with ScalaCheck {
+ "CombParserHelpers Specification".title
+
+ object ParserHelpers extends CombParserHelpers with Parsers
+ import ParserHelpers._
+
+ "The parser helpers" should {
+ "provide an isEof function returning true iff a char is end of file" in {
+ isEof('\u001a') must beTrue
+ }
+ "provide an notEof function returning true iff a char is not end of file" in {
+ notEof('\u001a') must beFalse
+ }
+ "provide an isNum function returning true iff a char is a digit" in {
+ isNum('0') must beTrue
+ }
+ "provide an notNum function returning true iff a char is not a digit" in {
+ notNum('0') must beFalse
+ }
+ "provide an wsc function returning true iff a char is a space character" in {
+ List(' ', '\t', '\r', '\n') foreach {wsc(_) must beTrue}
+ wsc('a') must beFalse
+ }
+ "provide a whitespace parser: white. Alias: wsc" in {
+ import WhiteStringGen._
+ val whiteParse = (s: String) => wsc(s).isInstanceOf[Success[_]]
+ forAll(whiteParse)
+ }
+ "provide a whiteSpace parser always succeeding and discarding its result" in {
+ import StringWithWhiteGen._
+ val whiteSpaceParse =
+ (s: String) => whiteSpace(s) must beLike {
+ case Success(x, y) => x.toString === "()"
+ }
+ forAll(whiteSpaceParse)
+ }
+ "provide an acceptCI parser to parse whatever string matching another string ignoring case" in {
+ import AbcdStringGen._
+ val ignoreCaseStringParse: Function2[String, String, Boolean] =
+ (s: String, s2: String) => acceptCI(s).apply(s2) match {
+ case Success(x, y) => s2.toUpperCase.startsWith(s.toUpperCase)
+ case _ => true
+ }
+ forAll(ignoreCaseStringParse)
+ }
+
+ "provide a digit parser - returning a String" in {
+ val isDigit: String => Boolean =
+ (s: String) => digit(s) match {
+ case Success(x, y) => s.matches("(?s)\\p{Nd}.*")
+ case _ => true
+ }
+ forAll(isDigit)
+ }
+ "provide an aNumber parser - returning an Int if succeeding" in {
+ val number: String => Boolean =
+ (s: String) => {
+ aNumber(s) match {
+ case Success(x, y) => s.matches("(?s)\\p{Nd}+.*")
+ case _ => true
+ }
+ }
+ forAll(number)
+ }
+
+ "provide a slash parser" in {
+ slash("/").get === '/'
+ slash("x") must beLike {case Failure(_, _) => 1 === 1}
+ }
+ "provide a colon parser" in {
+ colon(":").get === ':'
+ colon("x") must beLike {case Failure(_, _) => 1 === 1}
+ }
+ "provide a EOL parser which parses the any and discards any end of line character" in {
+ List("\n", "\r") map {
+ s =>
+ val result = EOL(s)
+ result.get.toString === "()"
+ result.next.atEnd must beTrue
+ }
+
+ success
+ }
+ val parserA = elem("a", (c: Char) => c == 'a')
+ val parserB = elem("b", (c: Char) => c == 'b')
+ val parserC = elem("c", (c: Char) => c == 'c')
+ val parserD = elem("d", (c: Char) => c == 'd')
+ def shouldSucceed[T](r: ParseResult[T]) = r match {
+ case Success(x, y) => true
+ case _ => false
+ }
+ "provide a permute parser succeeding if any permutation of given parsers succeeds" in {
+ def permuteParsers(s: String) = shouldSucceed(permute(parserA, parserB, parserC, parserD)(s))
+ val permutationOk = (s: String) => permuteParsers(s)
+
+ forAll(AbcdStringGen.abcdString)(permutationOk)
+ }
+ "provide a permuteAll parser succeeding if any permutation of the list given parsers, or a sublist of the given parsers succeeds" in {
+ def permuteAllParsers(s: String) = shouldSucceed(permuteAll(parserA, parserB, parserC, parserD)(s))
+ implicit def pick3Letters: Arbitrary[String] = AbcdStringGen.pickN(3, List("a", "b", "c"))
+
+ forAll { (s: String) =>
+ ((new scala.collection.immutable.StringOps(s)).nonEmpty) ==> permuteAllParsers(s)
+ }
+ }
+ "provide a repNN parser succeeding if an input can be parsed n times with a parser" in {
+ def repNNParser(s: String) = shouldSucceed(repNN(3, parserA)(s))
+ implicit def pick3Letters: Arbitrary[String] = AbcdStringGen.pickN(3, List("a", "a", "a"))
+
+ forAll { (s: String) =>
+ ((new scala.collection.immutable.StringOps(s)).nonEmpty) ==> repNNParser(s)
+ }
+ }
+ }
+}
+
+
+object AbcdStringGen {
+ implicit def abcdString: Gen[String] =
+ for (
+ len <- choose(4, 4);
+ string <- pick(len, List("a", "b", "c", "d"))
+ ) yield string.mkString("")
+
+ def pickN(n: Int, elems: List[String]) =
+ Arbitrary { for (string <- pick(n, elems)) yield string.mkString("") }
+}
+
+
+object WhiteStringGen {
+ def genWhite =
+ for (
+ len <- choose(1, 4);
+ string <- listOfN(len, frequency((1, Gen.const(" ")), (1, Gen.const("\t")), (1, Gen.const("\r")), (1, Gen.const("\n"))))
+ ) yield string.mkString("")
+
+ implicit def genWhiteString: Arbitrary[String] =
+ Arbitrary { genWhite }
+}
+
+
+object StringWithWhiteGen {
+ import WhiteStringGen._
+
+ def genStringWithWhite =
+ for (
+ len <- choose(1, 4);
+ string <- listOfN(len, frequency((1, Gen.const("a")), (2, Gen.const("b")), (1, genWhite)))
+ ) yield string.mkString("")
+
+ implicit def genString: Arbitrary[String] =
+ Arbitrary { genStringWithWhite }
+}
diff --git a/core/util/src/test/scala-3/net/liftweb/util/ConnectionIdentifierSpec.scala b/core/util/src/test/scala-3/net/liftweb/util/ConnectionIdentifierSpec.scala
new file mode 100644
index 000000000..1db7a3256
--- /dev/null
+++ b/core/util/src/test/scala-3/net/liftweb/util/ConnectionIdentifierSpec.scala
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2014 WorldWide Conferencing, LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.liftweb
+package util
+
+import org.specs2.mutable.Specification
+
+/**
+ * Systems under specification for ConnectionIdentifier.
+ */
+class ConnectionIdentifierSpec extends Specification {
+ "ConnectionIdentifier Specification".title
+
+ "Connection identifier" should {
+
+ "be set by property" in {
+ DefaultConnectionIdentifier.jndiName === "from_props"
+ }
+ }
+}
diff --git a/core/util/src/test/scala-3/net/liftweb/util/ControlHelpersSpec.scala b/core/util/src/test/scala-3/net/liftweb/util/ControlHelpersSpec.scala
new file mode 100644
index 000000000..a79d07804
--- /dev/null
+++ b/core/util/src/test/scala-3/net/liftweb/util/ControlHelpersSpec.scala
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2006-2011 WorldWide Conferencing, LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.liftweb
+package util
+
+import org.specs2.mutable.Specification
+
+import common._
+import ControlHelpers._
+
+
+/**
+ * Systems under specification for ControlHelpers.
+ */
+class ControlHelpersSpec extends Specification {
+ "ControlHelpers Specification".title
+
+ "the tryo function" should {
+ "return a Full can if the tested block doesn't throw an exception" in {
+ tryo { "valid" } must beEqualTo(Full("valid"))
+ }
+ val exception = new RuntimeException("ko")
+ def failureBlock = { throw exception; () }
+
+ "return a Failure if the tested block throws an exception" in {
+ tryo { failureBlock } must beEqualTo(Failure(exception.getMessage, Full(exception), Empty))
+ }
+ "return Empty if the tested block throws an exception whose class is in the ignore list - with one element" in {
+ tryo(classOf[RuntimeException]) { failureBlock } must beEqualTo(Empty)
+ }
+ "return Empty if the tested block throws an exception whose class is in the ignore list - with 2 elements" in {
+ tryo(List(classOf[RuntimeException], classOf[NullPointerException])) { failureBlock } must beEqualTo(Empty)
+ }
+ "trigger a callback function with the exception if the tested block throws an exception" in {
+ val callback = (e: Throwable) => { e === exception; () }
+ tryo(callback) { failureBlock }
+ success
+ }
+ "trigger a callback function with the exception if the tested block throws an exception even if it is ignored" in {
+ val callback = (e: Throwable) => { e === exception; () }
+ tryo(List(classOf[RuntimeException]), Full(callback)) { failureBlock }
+ success
+ }
+ "don't trigger a callback if the tested block doesn't throw an exception" in {
+ var x = false
+ val callback = (e: Throwable) => { x = true }
+ tryo(callback) { "valid" }
+ x === false
+ }
+ "don't trigger a callback if the tested block doesn't throw an exception, even with an ignore list" in {
+ var x = false
+ val callback = (e: Throwable) => { x = true }
+ tryo(List(classOf[RuntimeException]), Full(callback)) { "valid" }
+ x === false
+ }
+ }
+}
+
diff --git a/core/util/src/test/scala-3/net/liftweb/util/CssHelpersSpec.scala b/core/util/src/test/scala-3/net/liftweb/util/CssHelpersSpec.scala
new file mode 100644
index 000000000..b173c02ad
--- /dev/null
+++ b/core/util/src/test/scala-3/net/liftweb/util/CssHelpersSpec.scala
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2007-2015 WorldWide Conferencing, LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.liftweb
+package util
+
+import scala.xml._
+
+import org.specs2.mutable.Specification
+
+import common._
+
+class CssHelpersSpec extends Specification {
+ import CSSHelpers._
+
+ "CSSParser" should {
+ "leave most CSS alone" in {
+ val baseCss =
+ """
+ #booyan {
+ text-indent: 1em;
+ -moz-columns: 3;
+ -webkit-text-antialiasing: grayscale;
+ -magical-fake-thing: booyan;
+ superfake: but-still-reasonably-css-y;
+ }
+ """
+
+ CssUrlPrefixer("prefix").fixCss(baseCss) must beEqualTo(Full(baseCss))
+ }
+
+ "leave relative CSS urls alone" in {
+ val baseCss =
+ """
+ #booyan {
+ background: url(boom);
+ background-image: url('boom?bam,sloop#"shap%20bap');
+ image-set: url("http://boom.com/magic?'bam,sloop#bam%21bap")
+ }
+
+ .bam {
+ background-image: url("boom?bam,sloop#shap%20bap");
+ }
+ """
+
+ CssUrlPrefixer("prefix").fixCss(baseCss) must beEqualTo(Full(baseCss))
+ }
+
+ "prefix root-relative CSS urls with the specified prefix" in {
+ val baseCss =
+ """
+ |#booyan {
+ | background: url(/boom);
+ | background-image: url('/boom?bam,"sloop#shap%20bap');
+ | image-set: url("/boom.com/magic?bam,'sloop#bam%21bap")
+ |}""".stripMargin('|')
+
+ CssUrlPrefixer("prefix").fixCss(baseCss).openOrThrowException("test") ===
+ """
+ |#booyan {
+ | background: url(prefix/boom);
+ | background-image: url('prefix/boom?bam,"sloop#shap%20bap');
+ | image-set: url("prefix/boom.com/magic?bam,'sloop#bam%21bap")
+ |}""".stripMargin('|')
+ }
+
+ "fail on mismatched quotes or parens and report where it failed" in {
+ CssUrlPrefixer("prefix").fixCss("#boom { url('ha) }") must beLike {
+ case Failure(message, _, _) =>
+ message must contain("'ha")
+ }
+
+ CssUrlPrefixer("prefix").fixCss("#boom { url(\"ha) }") must beLike {
+ case Failure(message, _, _) =>
+ message must contain("\"ha")
+ }
+
+ CssUrlPrefixer("prefix").fixCss("#boom { url('ha' }") must beLike {
+ case Failure(message, _, _) =>
+ message must contain("ha' }")
+ }
+ }
+
+ // Escaped quotes-in-quotes currently fail. Maybe we want to support these?
+ }
+}
diff --git a/core/util/src/test/scala-3/net/liftweb/util/CssSelectorSpec.scala b/core/util/src/test/scala-3/net/liftweb/util/CssSelectorSpec.scala
new file mode 100644
index 000000000..131b2c297
--- /dev/null
+++ b/core/util/src/test/scala-3/net/liftweb/util/CssSelectorSpec.scala
@@ -0,0 +1,1027 @@
+/*
+ * Copyright 2010-2011 WorldWide Conferencing, LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.liftweb
+package util
+
+import org.specs2.matcher.XmlMatchers
+import org.specs2.mutable.Specification
+
+import common._
+import scala.xml._
+
+import Helpers._
+
+/**
+ * Systems under specification for CSS Selector.
+ */
+class CssSelectorSpec extends Specification with XmlMatchers {
+ "CSS Selector Specification".title
+
+ "CssSelector" should {
+ "fail for garbage input" in {
+ CssSelectorParser.parse(" 49234e23").isDefined === false
+ }
+
+ "select an id" in {
+ CssSelectorParser.parse("#foo").openOrThrowException("If the box is empty, we want a failure") ===
+ IdSelector("foo", Empty)
+ }
+
+ "a selector with cruft at the end must fail" in {
+ CssSelectorParser.parse("#foo I li**ke yaks").isDefined === false
+ }
+
+ ":yak must not parse" in {
+ CssSelectorParser.parse(":yak").isDefined === false
+ }
+
+ ":button must parse" in {
+ CssSelectorParser.parse(":button").openOrThrowException("If the box is empty, we want a failure") ===
+ AttrSelector("type", "button", Empty)
+ }
+
+
+ ":checkbox must parse" in {
+ CssSelectorParser.parse(":checkbox").openOrThrowException("If the box is empty, we want a failure") ===
+ AttrSelector("type", "checkbox", Empty)
+ }
+
+ ":file must parse" in {
+ CssSelectorParser.parse(":file").openOrThrowException("If the box is empty, we want a failure") ===
+ AttrSelector("type", "file", Empty)
+ }
+
+ ":password must parse" in {
+ CssSelectorParser.parse(":password").openOrThrowException("If the box is empty, we want a failure") ===
+ AttrSelector("type", "password", Empty)
+ }
+
+ ":radio must parse" in {
+ CssSelectorParser.parse(":radio").openOrThrowException("If the box is empty, we want a failure") ===
+ AttrSelector("type", "radio", Empty)
+ }
+
+ ":reset must parse" in {
+ CssSelectorParser.parse(":reset").openOrThrowException("If the box is empty, we want a failure") ===
+ AttrSelector("type", "reset", Empty)
+ }
+
+ ":submit must parse" in {
+ CssSelectorParser.parse(":submit").openOrThrowException("If the box is empty, we want a failure") ===
+ AttrSelector("type", "submit", Empty)
+ }
+
+ ":text must parse" in {
+ CssSelectorParser.parse(":text").openOrThrowException("If the box is empty, we want a failure") ===
+ AttrSelector("type", "text", Empty)
+ }
+
+ "select an id with attr subnodes" in {
+ CssSelectorParser.parse("#foo *[dog] ").openOrThrowException("If the box is empty, we want a failure") ===
+ IdSelector("foo", Full(AttrSubNode("dog")))
+ }
+
+ "select an id with no star attr subnodes" in {
+ CssSelectorParser.parse("#foo [woof] ").openOrThrowException("If the box is empty, we want a failure") ===
+ IdSelector("foo", Full(AttrSubNode("woof")))
+ }
+
+ "select an id with attr append subnodes" in {
+ CssSelectorParser.parse("#foo *[dog+] ").openOrThrowException("If the box is empty, we want a failure") ===
+ IdSelector("foo", Full(AttrAppendSubNode("dog")))
+ }
+
+ "select an id with no star attr append subnodes" in {
+ CssSelectorParser.parse("#foo [woof+] ").openOrThrowException("If the box is empty, we want a failure") ===
+ IdSelector("foo", Full(AttrAppendSubNode("woof")))
+ }
+
+ "select an id with attr append subnodes" in {
+ CssSelectorParser.parse("#foo *[dog!] ").openOrThrowException("If the box is empty, we want a failure") ===
+ IdSelector("foo", Full(AttrRemoveSubNode("dog")))
+ }
+
+ "select an id with no star attr append subnodes" in {
+ CssSelectorParser.parse("#foo [woof!] ").openOrThrowException("If the box is empty, we want a failure") ===
+ IdSelector("foo", Full(AttrRemoveSubNode("woof")))
+ }
+
+ "select attr/val pair" in {
+ CssSelectorParser.parse("frog=dog").openOrThrowException("test") ===
+ AttrSelector("frog", "dog", Empty)
+ }
+
+
+ "select attr/val pair single quote" in {
+ CssSelectorParser.parse("frog='dog food' *").openOrThrowException("test") ===
+ AttrSelector("frog", "dog food", Full(KidsSubNode()))
+ }
+
+
+ "select attr/val pair double quote" in {
+ CssSelectorParser.parse("frog=\"dog breath\"").openOrThrowException("test") ===
+ AttrSelector("frog", "dog breath", Empty)
+ }
+
+ "select name/val pair" in {
+ CssSelectorParser.parse("name=dog").openOrThrowException("test") ===
+ NameSelector("dog", Empty)
+ }
+
+ "select name/val pair" in {
+ CssSelectorParser.parse("@dog").openOrThrowException("test") ===
+ NameSelector("dog", Empty)
+ }
+
+ "select name/val pair" in {
+ CssSelectorParser.parse("@dog *").openOrThrowException("test") ===
+ NameSelector("dog", Full(KidsSubNode()))
+ }
+
+ "select name/val pair" in {
+ CssSelectorParser.parse("@dog -*").openOrThrowException("test") ===
+ NameSelector("dog", Full(PrependKidsSubNode()))
+ }
+
+ "select name/val pair surround" in {
+ CssSelectorParser.parse("@dog <*>").openOrThrowException("test") ===
+ NameSelector("dog", Full(SurroundKids()))
+ }
+
+ "select name/val pair" in {
+ CssSelectorParser.parse("@dog *+").openOrThrowException("test") ===
+ NameSelector("dog", Full(AppendKidsSubNode()))
+ }
+
+
+ "select name/val pair single quote" in {
+ CssSelectorParser.parse("name='dog food' *").openOrThrowException("test") ===
+ NameSelector("dog food", Full(KidsSubNode()))
+ }
+
+
+ "select name/val pair double quote" in {
+ CssSelectorParser.parse("name=\"dog breath\"").openOrThrowException("test") ===
+ NameSelector("dog breath", Empty)
+ }
+
+ "select a class" in {
+ CssSelectorParser.parse(".foo").openOrThrowException("If the box is empty, we want a failure") === ClassSelector("foo", Empty)
+ }
+
+ "select a class with subnodes" in {
+ CssSelectorParser.parse(".foo * ").openOrThrowException("If the box is empty, we want a failure") ===
+ ClassSelector("foo", Full(KidsSubNode()))
+ }
+
+ "Support selecting this node" in {
+ CssSelectorParser.parse(".foo ^^ ").openOrThrowException("If the box is empty, we want a failure") ===
+ ClassSelector("foo", Full(SelectThisNode(false)))
+ }
+
+ "Support selecting this node" in {
+ CssSelectorParser.parse(".foo ^* ").openOrThrowException("If the box is empty, we want a failure") ===
+ ClassSelector("foo", Full(SelectThisNode(true)))
+ }
+
+ "select a class with attr subnodes" in {
+ CssSelectorParser.parse(".foo *[dog] ").openOrThrowException("If the box is empty, we want a failure") ===
+ ClassSelector("foo", Full(AttrSubNode("dog")))
+ }
+
+ "select an id with no star attr subnodes" in {
+ CssSelectorParser.parse(".foo [woof] ").openOrThrowException("If the box is empty, we want a failure") ===
+ ClassSelector("foo", Full(AttrSubNode("woof")))
+ }
+
+ "select multiple depth" in {
+ CssSelectorParser.parse("div .foo [woof] ").openOrThrowException("If the box is empty, we want a failure") ===
+ EnclosedSelector(ElemSelector("div", Empty), ClassSelector("foo", Full(AttrSubNode("woof"))))
+ }
+
+ "select multiple depth with star" in {
+ CssSelectorParser.parse("div .foo * ").openOrThrowException("If the box is empty, we want a failure") ===
+ EnclosedSelector(ElemSelector("div", Empty), ClassSelector("foo", Full(KidsSubNode())))
+ }
+
+ "select multiple super depth with star" in {
+ CssSelectorParser.parse("span div .foo * ").openOrThrowException("If the box is empty, we want a failure") ===
+ EnclosedSelector(ElemSelector("span", Empty), EnclosedSelector(ElemSelector("div", Empty), ClassSelector("foo", Full(KidsSubNode()))))
+ }
+
+
+ }
+
+}
+
+class CssBindHelpersSpec extends Specification with XmlMatchers {
+
+ "css bind helpers" should {
+ "clear clearable" in {
+ ClearClearable() must ==/ ()
+ }
+
+ "substitute a String by id" in {
+ ("#foo" #> "hello").apply() must ==/ (hello)
+ }
+
+
+ "not duplicate classes" in {
+
+ def anchor(quesType: String, value: String) = {
+ (value)
+ }
+ var page = 1
+ var elements = List("1","2","3","4")
+
+ val xml =
)
+ }
+
+ "support modifying attributes along with body" in {
+ val org = foo
+ val func = "a [href]" #> "dog" & "a *" #> "bar"
+ val res = func(org)
+
+ res.toString === "bar"
+ }
+
+ "substitute a String by id" in {
+ ("#foo" replaceWith "hello").apply() must ==/ (hello)
+ }
+
+ "substitute a String by nested class" in {
+ ("div .foo" #> "hello").apply(
) must ==/ (
hello
)
+ }
+
+ "substitute a String by deep nested class" in {
+ ("#baz div .foo" #> "hello").apply(
+
) must ==/ (
hello
)
+ }
+
+ "insert a String by deep nested class" in {
+ ("#baz div .foo *" #> "hello").apply(
+
) must ==/ (
hello
)
+ }
+
+
+ "Only apply to the top elem" in {
+ val xf = "^ [href]" #> "wombat"
+
+ xf(stuff) must ==/ (stuff)
+ }
+
+
+
+ "Select a node" in {
+ ("#foo ^^" #> "hello").apply(
) must ==/ ()
+ }
+
+ "Another nested select" in {
+ val template =
+
) === (NodeSeq fromSeq {Text("bye")}{Text("hello")})
+ }
+
+ "bind href and None content" in {
+ val opt: Option[String] = None
+ val res = ("top *" #> opt &
+ "top [href]" #> "frog")(cat)
+
+ res.text === ""
+ (res \ "@href").text.mkString === "frog"
+ }
+
+ "bind href and Some content" in {
+ val opt: Option[String] = Some("Dog")
+ val res = ("top *" #> opt &
+ "top [href]" #> "frog")(cat)
+
+ res.text === "Dog"
+ (res \ "@href").text.mkString === "frog"
+ }
+
+ "bind href and Some content with multiple attrs" in {
+ val opt: Option[String] = Some("Dog")
+ val res = ("top *" #> opt &
+ "top [meow]" #> "woof" &
+ "top [href]" #> "frog")(cat)
+
+ res.text === "Dog"
+ (res \ "@href").text.mkString === "frog"
+ (res \ "@meow").text.mkString === "woof"
+ }
+
+ "option transform on *" in {
+ val opt: Option[String] = None
+ val res = ("* *" #> opt.map(ignore => "Dog")).apply(cat)
+ res.head ===
+ }
+
+ "append attribute to a class with spaces" in {
+ val stuff = List("a", "b")
+ val res = ("* [class+]" #> stuff).apply(cat)
+ (res \ "@class").text === "q a b"
+ }
+
+ "append attribute to an href" in {
+ val stuff = List("&a=b", "&b=d")
+ val res = ("* [href+]" #> stuff).apply(cat)
+ (res \ "@href").text === "q?z=r&a=b&b=d"
+ }
+
+ "remove an attribute from a class" in {
+ val func = ".foo [class!]" #> "andOther"
+
+ (func() \ "@class").text === "foo"
+ }
+
+ "remove an attribute from a class and the attribute if it's the only one left" in {
+ val func = ".foo [class!]" #> "foo"
+ val res = func()
+
+ (res \ "@class").length === 0
+ }
+
+ "don't merge class attribute" in {
+ val func = "p !!" #> 10
+ val res = func.apply(
Test
)
+ (res \ "@class").text === "replacement"
+ }
+
+ "merge other attributes when using don't merge class modification" in {
+ val func = "p !!" #> 10
+ val res = func.apply(
Test
)
+ (res \ "@data-one").text === "1"
+ (res \ "@data-two").text === "2"
+ (res \ "@class").text === "replacement"
+ }
+
+ "leave node class attribute for replacement without class" in {
+ // TODO: Since was agreed not to change current behaviour, create test for it (https://groups.google.com/forum/#!topic/liftweb/Nswcxoykspc)
+ val func = "p !!" #> 10
+ val res = func.apply(
Test
)
+ (res \ "@class").text === "first second"
+ }
+
+ "Remove a subnode's class attribute" in {
+
+ val func = ".removeme !!" #> ("td [class!]" #> "removeme")
+ val res = func.apply(
Hi
)
+
+ ((res \ "td") \ "@class").text === "fish"
+ }
+
+
+ "not remove a non-existant class" in {
+ val func = ".foo [class!]" #> "bar"
+ val res = func()
+
+ (res \ "@class").text === "foo"
+ }
+
+
+ "remove an attribute from an attribute" in {
+ val func = "span [href!]" #> "foo"
+ val res = func()
+
+ (res \ "@href").length === 0
+ }
+
+
+ "not remove a non-existant href" in {
+ val func = "span [href!]" #> "bar"
+ val res = func()
+
+ (res \ "@href").text === "foo bar"
+ }
+
+ "option transform on *" in {
+ val opt: Option[Int] = Full(44)
+ val res = ("* *" #> opt.map(ignore => "Dog")).apply(cat)
+ res must ==/ (Dog)
+ }
+
+
+ "Java number support" in {
+ val f = "a *" #> Full(java.lang.Long.valueOf(12))
+ val xml = Hello
+
+ f(xml) must ==/ (12)
+ }
+
+
+ "Surround kids" in {
+ val f = "a <*>" #>
+ val xml = Meow Cat woof
+
+ f(xml) must ==/ (Meow
+ ) === (NodeSeq fromSeq {Text("bye")}{Text("hello")})
+ }
+
+ "substitute multiple Strings with a List by id" in {
+ ("#foo" #> "hello" &
+ "#baz" #> List("bye", "bye"))(
Hello
) === (NodeSeq fromSeq {Text("bye")}{Text("bye")}{Text("hello")})
+ }
+
+ "substitute multiple Strings with a List by id" in {
+ (("#foo" replaceWith "hello") &
+ ("#baz" replaceWith List("bye", "bye")))(
Hello
) === (NodeSeq fromSeq {Text("bye")}{Text("bye")}{Text("hello")})
+ }
+
+
+ "substitute multiple Strings with a List of XML by id" in {
+ val answer = ("#foo" #> "hello" &
+ "#baz" #> List[NodeSeq](, Meow))(
Hello
)
+
+ (answer \ "i").length === 2
+ (answer \ "i")(0) must ==/ ()
+ (answer \ "i")(1) must ==/ (Meow)
+ }
+
+ "substitute multiple Strings with a List of XML by id" in {
+ val answer = (("#foo" replaceWith "hello") &
+ ("#baz" replaceWith List[NodeSeq](, Meow)))(
Hello
)
+
+ (answer \ "i").length === 2
+ (answer \ "i")(0) must ==/ ()
+ (answer \ "i")(1) must ==/ (Meow)
+ }
+
+ "substitute by name" in {
+ val answer = ("name=moose" #> ).apply (
+ )
+
+ (answer \ "input")(0) must ==/ ()
+ }
+
+
+ "Deal with NodeSeq as a NodeSeq" in {
+ val f = "h6 *" #> ((Text("Some awesome ") ++ text ++ Text(" here.")): NodeSeq)
+ val xml =
Dude, where's my car?
+
+ val res = f(xml)
+ res must ==/ (
Some awesome text here.
)
+ }
+
+ "substitute by name" in {
+ val answer = ("name=moose" replaceWith ).apply (
+ )
+
+ (answer \ "input")(0) must ==/ ()
+ }
+
+
+ "substitute by name with attrs" in {
+ val answer = ("name=moose" #> ).apply (
+ )
+
+ (answer \ "input")(0) must ==/ ()
+ }
+
+ "substitute by name with attrs" in {
+ val answer = ("name=moose" replaceWith ).apply (
+ )
+
+ (answer \ "input")(0) must ==/ ()
+ }
+
+
+ "substitute by a selector with attrs" in {
+ val answer = ("cute=moose" #> ).apply (
+ )
+
+ (answer \ "input")(0) must ==/ ()
+ }
+
+ "substitute by a selector with attrs" in {
+ val answer = ("cute=moose" replaceWith ).apply (
+ )
+
+ (answer \ "input")(0) must ==/ ()
+ }
+
+ "Map of funcs" in {
+ val func: NodeSeq => NodeSeq = "#horse" #> List(1,2,3).map(".item *" #> _)
+ val answer: NodeSeq = func(
frogi
)
+
+ answer must ==/ (
frog1
frog2
frog3
)
+
+ }
+
+ "maintain unique id attributes provided by transform" in {
+ val func = ".thinglist *" #>
+ (".thing" #> List("xx1", "xx2", "xx2", "xx2", "xx4").map(t => {
+ ".thing [id]" #> t
+ })
+ )
+ val answer = func(
)
+
+ answer must ==/ (
)
+ }
+
+ "merge classes" in {
+ val answer = ("cute=moose" #> ).apply (
+ )
+
+ (answer \ "input")(0) must ==/ ()
+ }
+
+
+ "merge classes" in {
+ val answer = ("cute=moose" replaceWith ).apply (
+ )
+
+ (answer \ "input")(0) must ==/ ()
+ }
+
+
+
+
+ "list of strings" in {
+ val answer = ("#moose *" #> List("a", "b", "c", "woof") &
+ ClearClearable).apply (
+
+
first
+
second
+
Third
+
)
+
+ val lis = (answer \ "li").toList
+
+ lis.length === 4
+
+ lis(0) must ==/ (
a
)
+ lis(3) must ==/ (
woof
)
+ }
+
+
+ "list of Nodes" in {
+ val answer = ("#moose *" #> List[NodeSeq]("a", Text("b"), Text("c"), woof) &
+ ClearClearable).apply (
+
+
first
+
second
+
Third
+
)
+
+ val lis = (answer \ "li").toList
+
+ lis.length === 4
+
+ lis(0) must ==/ (
)
+
+
+ (answer \ "a" \ "@href").text === "Hi"
+ (answer \\ "li").length === 0
+ }
+
+
+ }
+}
+
+
+/**
+ * This class doesn't actually perform any tests, but insures that
+ * the implicit conversions work correctly
+ */
+object CheckTheImplicitConversionsForToCssBindPromoter {
+ val bog = new CssBindPromoter(Empty, Empty)
+
+ "foo" #> "baz"
+
+ bog #> "Hello"
+ bog #>
+ bog #> 1
+ bog #> Symbol("foo")
+ bog #> 44L
+ bog #> 1.22
+ bog #> false
+
+ bog #> List()
+ bog #> Full()
+ val e: Box[String] = Empty
+ bog #> e
+ bog #> Some()
+ val n: Option[String] = None
+ bog #> n
+
+
+ bog #> List("Hello")
+ bog #> List(1.22)
+ bog #> List(44L)
+ bog #> List(1)
+ bog #> Full("Dog")
+ bog #> Some("Moo")
+
+
+ bog #> List((null: Bindable))
+ bog #> Full((null: Bindable))
+ bog #> Some((null: Bindable))
+
+ bog #> nsToNs _
+ bog #> nsToOptNs _
+ bog #> nsToBoxNs _
+ bog #> nsToSeqNs _
+
+ bog #> nsToString _
+ bog #> nsToOptString _
+ bog #> nsToBoxString _
+ bog #> nsToSeqString _
+
+ val nsf: NodeSeq => NodeSeq = bog #> "Hello" &
+ bog #> &
+ bog #> 1 &
+ bog #> Symbol("foo") &
+ bog #> 44L &
+ bog #> false
+
+ "foo" #> "Hello"
+ "foo" #>
+ "foo" #> 1
+ "foo" #> Symbol("foo")
+ "foo" #> 44L
+ "foo" #> false
+
+ "foo" #> List()
+ "foo" #> Full()
+ "foo" #> Some()
+
+
+ "foo" #> List("Hello")
+ "foo" #> Full("Dog")
+ "foo" #> Some("Moo")
+
+
+ "foo" #> List((null: Bindable))
+ "foo" #> Full((null: Bindable))
+ "foo" #> Some((null: Bindable))
+
+ "foo" #> nsToNs _
+ "foo" #> nsToOptNs _
+ "foo" #> nsToBoxNs _
+ "foo" #> nsToSeqNs _
+
+ "foo" #> nsToString _
+ "foo" #> nsToOptString _
+ "foo" #> nsToBoxString _
+ "foo" #> nsToSeqString _
+
+ "#foo" #> Set("a", "b", "c")
+
+ val nsf2: NodeSeq => NodeSeq = "foo" #> "Hello" &
+ "foo" #> &
+ "foo" #> 1 &
+ "foo" #> Symbol("foo") &
+ "foo" #> 44L &
+ "foo" #> false
+
+ "bar" #> List("1","2","3").map(s => "baz" #> s)
+
+ "bar" #> Full(1).map(s => ("baz" #> s): CssBindFunc)
+ "bar" #> Some(1).map(s => ("baz" #> s): CssBindFunc)
+
+
+
+ def nsToNs(in: NodeSeq): NodeSeq = in
+ def nsToOptNs(in: NodeSeq): Option[NodeSeq] = Some(in)
+ def nsToBoxNs(in: NodeSeq): Box[NodeSeq] = Full(in)
+ def nsToSeqNs(in: NodeSeq): Seq[NodeSeq] = List(in)
+
+ def nsToString(in: NodeSeq): String = in.text
+ def nsToOptString(in: NodeSeq): Option[String] = Some(in.text)
+ def nsToBoxString(in: NodeSeq): Box[String] = Full(in.text)
+ def nsToSeqString(in: NodeSeq): Seq[String] = List(in.text)
+}
diff --git a/core/util/src/test/scala-3/net/liftweb/util/CurrencyZoneSpec.scala b/core/util/src/test/scala-3/net/liftweb/util/CurrencyZoneSpec.scala
new file mode 100644
index 000000000..3dedf8561
--- /dev/null
+++ b/core/util/src/test/scala-3/net/liftweb/util/CurrencyZoneSpec.scala
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2007-2011 WorldWide Conferencing, LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.liftweb
+package util
+
+import org.specs2.mutable.Specification
+
+
+/**
+ * Systems under specification for CurrencyZone.
+ */
+class CurrencyZoneSpec extends Specification {
+ "CurrencyZone Specification".title
+
+ "Australian money" should {
+
+ "not equal to other money" in {
+ val auBoolean = AU(4.42) != US(4.42)
+ auBoolean === true
+ }
+
+ "be not equal to a different amount of its own money" in {
+ val auBoolean = AU(4.48) == AU(4.481)
+ auBoolean === false
+ }
+
+
+ "be equal to the same amount of its own money" in {
+ val auBoolean = AU(4.42) == AU(4.42)
+ auBoolean === true
+ }
+
+ "be comparable not gt" in {
+ val auBoolean = AU(4.42) > AU(4.420000)
+ auBoolean === false
+ }
+
+ "be creatable" in {
+ AU(20.1).get must beMatching ("20.10")
+ }
+
+ "be addable" in {
+ val au = AU(20.68) + AU(3.08)
+ au.get must beMatching ("23.76")
+ }
+
+ "be subtractable" in {
+ val au = AU(23.76) - AU(3.08)
+ au.get must beMatching ("20.68")
+ }
+
+ "be mutipliable" in {
+ val au = AU(20.68) * 3
+ au.get must beMatching ("62.04")
+ }
+
+ "be divisable" in {
+ val au = AU(20.68) / AU(3)
+ au.get must beMatching ("6.89")
+ }
+
+
+ "be comparable gt" in {
+ val auBoolean = AU(20.68) > AU(3)
+ auBoolean === true
+ }
+
+ "be comparable lt" in {
+ val auBoolean = AU(3.439) < AU(3.44)
+ auBoolean === true
+ }
+
+ "be comparable lt or eq" in {
+ val auBoolean = AU(20.68) <= AU(20.68)
+ auBoolean === true
+ }
+ }
+
+}
+
diff --git a/core/util/src/test/scala-3/net/liftweb/util/Html5ParserSpec.scala b/core/util/src/test/scala-3/net/liftweb/util/Html5ParserSpec.scala
new file mode 100644
index 000000000..f3726673f
--- /dev/null
+++ b/core/util/src/test/scala-3/net/liftweb/util/Html5ParserSpec.scala
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2006-2011 WorldWide Conferencing, LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.liftweb
+package util
+
+import scala.xml.Elem
+
+import org.specs2.mutable.Specification
+import org.specs2.execute.PendingUntilFixed
+
+import common._
+import Helpers._
+
+
+/**
+ * Systems under specification for Html5 Parser.
+ */
+class Html5ParserSpec extends Specification with PendingUntilFixed with Html5Parser with Html5Writer {
+ "Html5Parser Specification".title
+
+ "Htm5 Writer" should {
+ "Write &" in {
+ toString() === """"""
+ }
+
+ "ignore attributes that are null" in {
+ toString() === """"""
+ }
+ }
+
+ "Html5 Parser" should {
+ val pages = for {
+ page1 <- tryo(readWholeStream(getClass.getResourceAsStream("Html5ParserSpec.page1.html"))).filter(_ ne null)
+ page2 <- tryo(readWholeStream(getClass.getResourceAsStream("Html5ParserSpec.page2.html"))).filter(_ ne null)
+ page3 <- tryo(readWholeStream(getClass.getResourceAsStream("Html5ParserSpec.page3.html"))).filter(_ ne null)
+ } yield (page1, page2, page3)
+
+ pages match {
+ case Full(p) =>
+ val (page1, page2, page3) = (new String(p._1), new String(p._2), new String(p._3))
+
+ "parse valid page type1" in {
+ val parsed = parse(page1).openOrThrowException("Test")
+ (parsed \\ "script").length must beGreaterThanOrEqualTo(4)
+ }
+
+ "parse valid page type2" in {
+ val parsed = parse(page2).openOrThrowException("Test")
+ (parsed \\ "script").length must beGreaterThanOrEqualTo(4)
+ }
+
+ "fail to parse invalid page type3" in {
+ val parsed = parse(page3)
+ parsed must beAnInstanceOf[Failure]
+ }.pendingUntilFixed
+
+ case _ =>
+ failure("Failed loading test files") // TODO: Improve error message
+ }
+
+ "change to " in {
+ val parsed = parse("
123
").openOrThrowException("Test")
+ val heads = parsed \\ "head"
+ heads.length === 1
+ heads.text === "123"
+ (heads(0).asInstanceOf[Elem].prefix == null) === true
+ }.pendingUntilFixed
+
+ "Parse stuff with lift: namespace" in {
+ val parsed = parse("""""")
+ val e = parsed.openOrThrowException("Test").asInstanceOf[Elem]
+ e.prefix === "lift"
+ e.label === "surround"
+ (parsed.openOrThrowException("Test") \ "@with").text === "dog"
+ }
+
+ "Parse stuff without lift: namespace" in {
+ val parsed = parse("""
""")
+ val e = parsed.openOrThrowException("Test").asInstanceOf[Elem]
+ e.label === "div"
+ (parsed.openOrThrowException("Test") \ "@with").text === "dog"
+ }
+ }
+
+}
+
diff --git a/core/util/src/test/scala-3/net/liftweb/util/HtmlHelpersSpec.scala b/core/util/src/test/scala-3/net/liftweb/util/HtmlHelpersSpec.scala
new file mode 100644
index 000000000..6369ed2df
--- /dev/null
+++ b/core/util/src/test/scala-3/net/liftweb/util/HtmlHelpersSpec.scala
@@ -0,0 +1,315 @@
+/*
+ * Copyright 2007-2011 WorldWide Conferencing, LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.liftweb
+package util
+
+import scala.xml._
+
+import org.specs2.matcher.XmlMatchers
+import org.specs2.mutable.Specification
+
+import common._
+
+/**
+ * Systems under specification for HtmlHelpers.
+ */
+class HtmlHelpersSpec extends Specification with HtmlHelpers with XmlMatchers {
+ "HtmlHelpers Specification".title
+
+ "findBox" should {
+ "find an id" in {
+ val xml = Dog
+
+ findBox(xml) {
+ e => e.attribute("id").
+ filter(_.text == "3").
+ map(i => e)
+ }.openOrThrowException("Test") must ==/ ()
+ }
+
+ "not find an ide" in {
+ val xml = Dog
+
+ findBox(xml) {
+ e => e.attribute("id").
+ filter(_.text == "3").map(i => e)
+ } must beEqualTo(Empty)
+ }
+
+
+ "not find a the wrong id" in {
+ val xml = Dog
+
+ findBox(xml) {
+ e => e.attribute("id").
+ filter(_.text == "3").map(i => e)
+ } must beEqualTo(Empty)
+ }
+ }
+
+ "findOption" should {
+ "find an id" in {
+ val xml = Dog
+
+ findOption(xml) {
+ e => e.attribute("id").
+ filter(_.text == "3").map(i => e)
+ }.get must ==/ ()
+ }
+
+ "not find an ide" in {
+ val xml = Dog
+
+ findOption(xml) {
+ e => e.attribute("id").
+ filter(_.text == "3").map(i => e)
+ } must beNone
+ }
+
+
+ "not find a the wrong id" in {
+ val xml = Dog
+
+ findOption(xml) {
+ e => e.attribute("id").
+ filter(_.text == "3").map(i => e)
+ } must beNone
+ }
+ }
+
+ "findId" should {
+ val xml =
+
+ Boom
+ Other boom
+
+
+ "find the element with a requested id in a NodeSeq" in {
+ findId(xml, "boom") must beLike {
+ case Some(element) =>
+ element must ==/(Boom)
+ }
+ }
+
+ "provide a None if a requested id is not found in the NodeSeq" in {
+ findId(xml, "explode") must beNone
+ }
+
+ "find the first id in a NodeSeq when no id is requested" in {
+ findId(xml) must beEqualTo(Full("boom"))
+ }
+
+ "provide an Empty if no ide is foud in a NodeSeq when no id is requested" in {
+ findId() must beEqualTo(Empty)
+ }
+ }
+
+ "head removal" should {
+ "remove " in {
+ Helpers.stripHead(hello) must ==/(hello)
+ }
+
+ "ignore non-head" in {
+ Helpers.stripHead(hello) must ==/(hello)
+ }
+
+ "String subhead" in {
+ Helpers.stripHead(hello) must ==/(hello)
+ }
+ }
+
+ "removeAttribute" should {
+ val element =
+
+ "remove the specified attribute from a provided element" in {
+ val removed = removeAttribute("attribute", element)
+
+ removed must ==/()
+ }
+
+ "remove the specified attribute from a provided MetaData list" in {
+ val removed = removeAttribute("attribute", element.attributes)
+
+ (removed("attribute") must beNull) and
+ (removed("otherAttribute") must beEqualTo(Text("good-bye")))
+ }
+ }
+
+ "addCssClass" should {
+ "add a new attribute if no class attribute exists" in {
+ (addCssClass("foo", ) \ "@class").text must beEqualTo("foo")
+ }
+
+ "append to an existing class attribute if it already exists" in {
+ (addCssClass("foo", ) \ "@class").text must beEqualTo("dog foo")
+ }
+ }
+
+ "ensureUniqueId" should {
+ "leave things unchanged if no ids collide" in {
+ val xml =
+
+
+
+
+
+ val uniqued = {ensureUniqueId(xml).head}
+
+ (uniqued must \("boom", "id" -> "thing")) and
+ (uniqued must \\("hello", "id" -> "other-thing")) and
+ (uniqued must \\("bye", "id" -> "third-thing"))
+ }
+
+ "strip the ids if elements have an id matching a previous one" in {
+ val xml =
+ Group(
+
+
+
+ )
+
+ val uniqued = NodeSeq.seqToNodeSeq(ensureUniqueId(xml).flatten)
+
+ uniqued must ==/(
+
+
+
+ )
+ }
+
+ "leave child element ids alone even if they match the ids of the root element or each other" in {
+ val xml =
+
+
+
+
+
+ val uniqued = {ensureUniqueId(xml).head}
+
+ (uniqued must \\("hello", "id" -> "thing")) and
+ (uniqued must \\("bye", "id" -> "thing"))
+ }
+ }
+
+ "deepEnsureUniqueId" should {
+ "leave things unchanged if no ids collide" in {
+ val xml =
+
+
+
+
+
+ val uniqued = {deepEnsureUniqueId(xml).head}
+
+ (uniqued must \("boom", "id" -> "thing")) and
+ (uniqued must \\("hello", "id" -> "other-thing")) and
+ (uniqued must \\("bye", "id" -> "third-thing"))
+ }
+
+ "strip the ids if elements have an id matching a previous one" in {
+ val xml =
+ Group(
+
+
+
+ )
+
+ val uniqued = NodeSeq.seqToNodeSeq(deepEnsureUniqueId(xml).flatten)
+
+ uniqued must ==/(
+
+
+
+ )
+ }
+
+ "strip child element ids alone if they match the ids of the root element or each other" in {
+ val xml =
+
+
+ Boom
+
+
+
+
+
+ val uniqued = {deepEnsureUniqueId(xml).head}
+
+ uniqued must ==/(
+
+
+
+ Boom
+
+
+
+
+
+ )
+ }
+ }
+
+ "ensureId" should {
+ "if the first element has an id, replace it with the specified one" in {
+ val xml =
+
+
+ Boom
+
+
+
+
+
+ val uniqued = {ensureId(xml, "other-thinger").head}
+
+ uniqued must \("boom", "id" -> "other-thinger")
+ }
+
+ "if the first element has no id, give it the specified one" in {
+ val xml =
+
+
+ Boom
+
+
+
+
+
+ val uniqued = {ensureId(xml, "other-thinger").head}
+
+ uniqued must \("boom", "id" -> "other-thinger")
+ }
+
+ "not affect other elements" in {
+ val xml =
+ Group(
+
+
+
+ )
+
+ val uniqued = NodeSeq.seqToNodeSeq(ensureId(xml, "other-thinger").flatten)
+
+ uniqued must ==/(
+
+
+
+ )
+ }
+ }
+}
+
diff --git a/core/util/src/test/scala-3/net/liftweb/util/HttpHelpersSpec.scala b/core/util/src/test/scala-3/net/liftweb/util/HttpHelpersSpec.scala
new file mode 100644
index 000000000..9d3e5978e
--- /dev/null
+++ b/core/util/src/test/scala-3/net/liftweb/util/HttpHelpersSpec.scala
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2006-2011 WorldWide Conferencing, LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.liftweb
+package util
+
+import org.specs2.matcher.XmlMatchers
+import org.specs2.mutable.Specification
+
+import HttpHelpers._
+
+
+/**
+ * Systems under specification for HttpHelpers.
+ */
+class HttpHelpersSpec extends Specification with HttpHelpers with ListHelpers with StringHelpers with XmlMatchers {
+ "HttpHelpers Specification".title
+
+ "Http helpers" should {
+ "urlEncode and urlDecode functions" >> {
+ urlDecode(urlEncode("hello world")) === "hello world"
+ urlEncode(urlDecode("hello+world")) === "hello+world"
+ }
+ "a paramsToUrlParams function to translate a map of parameters to a URL query" in {
+ paramsToUrlParams(List(("firstname", "hello"), ("lastname", "world"))) === "firstname=hello&lastname=world"
+ }
+ "an appendParams function to add parameters to a URL query" in {
+ "creating the param list with ? if there are no existing params" in {
+ appendParams("www.helloworld.com/params", List(("firstname", "hello"), ("lastname", "world"))) ===
+ "www.helloworld.com/params?firstname=hello&lastname=world"
+ }
+ "appending the param list with & if there are some already" in {
+ appendParams("www.helloworld.com/params?firstname=hello", List(("lastname", "world"))) ===
+ "www.helloworld.com/params?firstname=hello&lastname=world"
+ }
+ "returning the url if no param list is passed" in {
+ appendParams("www.helloworld.com/params", Nil) === "www.helloworld.com/params"
+ }
+ }
+ "a couldBeHtml function" in {
+ "returning true if there is a pair (Content-Type, text/html)" in {
+ couldBeHtml(Map(("Content-Type", "text/html"))) must beTrue
+ }
+ "returning true if there is a pair (Content-Type, Application/xhtml+xml). The check is case insensitive" in {
+ couldBeHtml(Map(("Content-Type", "Application/xhtml+XML"))) must beTrue
+ }
+ "returning false if the content type is something else (Content-Type, application/jpeg)" in {
+ couldBeHtml(Map(("Content-Type", "application/jpeg"))) must beFalse
+ }
+ "returning true if there is no a pair starting with Content-Type" in {
+ couldBeHtml(Map(("no content type", "text/html"))) must beTrue
+ }
+ }
+ "a noHtmlTag" in {
+ "returning true if a xml node doesn't contain the html tag" in {
+ noHtmlTag() must beTrue
+ }
+ "returning false if a xml node contains the html tag" in {
+ noHtmlTag() must beFalse
+ }
+ }
+ "a toHashMap function transforming a Map to a mutable HashMap" in {
+ toHashMap(Map(1 -> 2, 3 -> 4)) must haveClass[scala.collection.mutable.HashMap[Int, Int]]
+ }
+ "an insureField function" in {
+ "checking that the appropriate fields are in the header" in {
+ insureField(List(("name", "hello")), List(("name", "hello"))) === List(("name", "hello"))
+ }
+ "checking that the appropriate fields are in the header, adding them if necessary" in {
+ insureField(List(("name2", "hello")), List(("name", "hello"))) === List(("name", "hello"), ("name2", "hello"))
+ }
+ }
+ "an implicit definition to transform a pair to an UnprefixedAttribute" in {
+ pairToUnprefixed(("value", 1)).apply("value").toString === "1"
+ }
+ "a findOrAddId function" in {
+ "returning an element and its id if found" in { findOrAddId() === (, "1") }
+ "returning an element with a random id if not found" in {
+ val (e, id) = findOrAddId()
+ e must \("@id")
+ // id must beMatching("R\\[a-zA-Z0-9]*")
+ }
+ }
+ }
+ // currentSus is no longer part of Specification in 1.6 def provide(e: =>Example) = { currentSus.verb += " provide"; e }
+}
+
diff --git a/core/util/src/test/scala-3/net/liftweb/util/IoHelpersSpec.scala b/core/util/src/test/scala-3/net/liftweb/util/IoHelpersSpec.scala
new file mode 100644
index 000000000..144e51da7
--- /dev/null
+++ b/core/util/src/test/scala-3/net/liftweb/util/IoHelpersSpec.scala
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2017 WorldWide Conferencing, LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.liftweb
+package util
+
+import org.specs2.mutable.Specification
+
+import common._
+import Helpers._
+
+import java.nio.charset.StandardCharsets
+import java.nio.file.{Files, Path}
+
+class IoHelpersSpec extends Specification with IoHelpers {
+ "IoHelpers Specification".title
+
+ "Io helpers" should {
+
+ "readWholeFile properly" in {
+ // Copy a resource file to the tmp directory so we can refer to it as a Path
+ val resourceAsPath: Box[Path] = {
+ for {
+ bytes <- tryo(readWholeStream(getClass.getResourceAsStream("IoHelpersSpec.txt"))).filter(_ ne null)
+ text <- tryo(new String(bytes))
+ path = {
+ val tempFile = Files.createTempFile(s"IoHelpersSpec_${nextFuncName}", ".tmp")
+ Files.write(tempFile, text.getBytes(StandardCharsets.UTF_8))
+ tempFile
+ }
+ } yield path
+ }
+
+ resourceAsPath.isDefined === true
+
+ resourceAsPath.foreach { path =>
+ val pathContents = new String(readWholeFile(path)).trim
+ Files.delete(path)
+ pathContents === "IoHelpersSpec"
+ }
+
+ success
+ }
+ }
+}
diff --git a/core/util/src/test/scala-3/net/liftweb/util/JsonCommandSpec.scala b/core/util/src/test/scala-3/net/liftweb/util/JsonCommandSpec.scala
new file mode 100644
index 000000000..6177f1a4b
--- /dev/null
+++ b/core/util/src/test/scala-3/net/liftweb/util/JsonCommandSpec.scala
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2008-2011 WorldWide Conferencing, LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.liftweb
+package util
+
+import org.specs2.mutable.Specification
+
+import org.json4s._
+import org.json4s.native.JsonParser
+import org.json4s.native.JsonMethods._
+
+
+/**
+ * Systems under specification for JsonCommand.
+ */
+class JsonCommandSpec extends Specification {
+ "JsonCommand Specification".title
+
+ private def parse(in: String): JValue = JsonParser.parse(in)
+
+ "The JsonCommand object" should {
+ "return None for non-commands" in {
+ JsonCommand.unapply(parse("""{"foo": "bar", "baz": false, "params": "moose"} """)) === None
+ }
+
+ "return None for non-params" in {
+ JsonCommand.unapply(parse("""{"command": "frog", "foo": "bar", "baz": false} """)) === None
+ }
+
+ "Parse even if target missing" in {
+ JsonCommand.unapply(parse("""{"command": "frog", "foo": "bar", "params": 99} """)) === Some(("frog", None, JInt(99)))
+ }
+
+ "Parse the whole thing" in {
+ JsonCommand.unapply(parse("""{"command": "frog", "target": "spud", "foo": "bar", "params": 982, "baz": false} """)) ===
+ Some(("frog", Some("spud"), JInt(982)))
+ }
+ }
+}
diff --git a/core/util/src/test/scala-3/net/liftweb/util/ListHelpersSpec.scala b/core/util/src/test/scala-3/net/liftweb/util/ListHelpersSpec.scala
new file mode 100644
index 000000000..f391c16a6
--- /dev/null
+++ b/core/util/src/test/scala-3/net/liftweb/util/ListHelpersSpec.scala
@@ -0,0 +1,197 @@
+/*
+ * Copyright 2007-2011 WorldWide Conferencing, LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.liftweb
+package util
+
+import org.specs2.mutable.Specification
+
+import common._
+
+
+/**
+ * Systems under specification for ListHelpers.
+ */
+class ListHelpersSpec extends Specification with ListHelpers {
+ "ListHelpers Specification".title
+
+ "ListHelpers.delta" should {
+ "insert after 2" in {
+ val ret = delta(List(1, 2, 4, 5), List(1, 2, 3, 4, 5)) {
+ case InsertAfterDelta(3, 2) => "ok"
+ case _ => "not ok"
+ }
+ ret === List("ok")
+ }
+
+ "prepend and append 2,4, 99" in {
+ val ret = delta(List(2, 4, 99), List(1, 2, 3, 4, 5)) {
+ case InsertAfterDelta(3, 2) => "ok"
+ case AppendDelta(5) => "ok5"
+ case RemoveDelta(99) => "99"
+ case InsertAtStartDelta(1) => "1"
+ case InsertAfterDelta(5, 4) => "ok5"
+ case _ => "fail"
+ }
+ ret === List("1", "ok", "ok5", "99")
+ }
+
+ "prepend and append" in {
+ val ret = delta(List(4, 2, 99), List(1, 2, 3, 4, 5)) {
+ case InsertAfterDelta(3, 2) => "ok"
+ case InsertAfterDelta(4, 3) => "ok3"
+ case RemoveDelta(4) => "r4"
+ case AppendDelta(5) => "ok5"
+ case RemoveDelta(99) => "99"
+ case InsertAtStartDelta(1) => "1"
+ case InsertAfterDelta(5, 4) => "ok5"
+ case _ => "fail"
+ }
+ ret === List("1", "r4", "ok", "ok3", "ok5", "99")
+ }
+ }
+
+ "The ListHelpers first_? function" should {
+ "return an Empty can if the list is empty" in {
+ first_?((Nil: List[Int]))((i: Int) => true) must beEqualTo(Empty)
+ }
+ "return an Empty can if no element in the list satisfies the predicate" in {
+ first_?(List(1, 2, 3))((i: Int) => i < 0) must beEqualTo(Empty)
+ }
+ "return a Full can with the first element in the list satisfying the predicate" in {
+ first_?(List(1, 2, 3))((i: Int) => i > 0) must beEqualTo(Full(1))
+ }
+ }
+
+ "The ListHelpers first function" should {
+ "return an Empty can if the list is empty" in {
+ first((Nil: List[Int]))((i: Int) => Full(1)) must beEqualTo(Empty)
+ }
+ "return an Empty can if no element in the list returns a Full can when applied a function" in {
+ first(List(1, 2, 3))((i: Int) => Empty) must beEqualTo(Empty)
+ }
+ "return the first Full can returned by a function f over the list elements" in {
+ val f = (i: Int) => i >= 2 match {case true => Full(3) case false => Empty}
+ first(List(1, 2, 3))(f) must beEqualTo(Full(3))
+ }
+ }
+
+ "The ciGet function on Lists of pairs of string" should {
+ "return Empty if the list is Nil" in {
+ (Nil: List[(String, String)]).ciGet("") must beEqualTo(Empty)
+ }
+ "return Empty if no pair has the key as its first element" in {
+ List(("one", "1"), ("two", "2")).ciGet("three") must beEqualTo(Empty)
+ }
+ "return a Full can with the first second value of a pair matching the key" in {
+ List(("one", "1"), ("two", "2")).ciGet("one") must beEqualTo(Full("1"))
+ }
+ "return a Full can with the first second value of a pair matching the key case-insensitively" in {
+ List(("one", "1"), ("two", "2"), ("two", "3")).ciGet("two") must beEqualTo(Full("2"))
+ }
+ }
+
+ "The ListHelpers enumToList and enumToStringList functions" should {
+ "convert a java enumeration to a List" in {
+ val v: java.util.Vector[Int] = new java.util.Vector[Int]
+ v.add(1);
+ v.add(2)
+ enumToList(v.elements) === List(1, 2)
+ }
+ "convert a java enumeration containing any kind of object to a List of Strings" in {
+ val v: java.util.Vector[Any] = new java.util.Vector[Any]
+ v.add(1);
+ v.add("hello")
+ enumToStringList(v.elements) === List("1", "hello")
+ }
+ }
+
+ "The ListHelpers head function (headOr on a list object)" should {
+ "return the first element of a list" in {
+ List(1).headOr(2) === 1
+ }
+ "return a default value if the list is empty" in {
+ head(Nil, 2) === 2
+ }
+ "not evaluate the default valueif list is not empty" in {
+ head(List(1), {sys.error("stop"); 2}) === 1
+ }
+ }
+
+ "The ListHelpers listIf function" should {
+ "create a List containing an element if the predicate is true" in {
+ listIf(true)(1) === List(1)
+ }
+ "return an empty List if the predicate is false" in {
+ listIf(false)(1) === Nil
+ }
+ "not evaluate its argument if the predicate is false" in {
+ listIf(false)({sys.error("stop"); 1}) === Nil
+ }
+ }
+
+ "The ListHelpers rotateList function (rotate method on a List object)" should {
+ "create a List of all the circular permutations of a given list" in {
+ List(1, 2, 3).rotate === List(List(1, 2, 3), List(2, 3, 1), List(3, 1, 2))
+ }
+ }
+
+ "The ListHelpers permuteList function (permute method on a List object)" should {
+ "create a List of all the permutations of a given list" in {
+ List(1, 2, 3).permute ===
+ List(List(1, 2, 3), List(1, 3, 2), List(2, 3, 1), List(2, 1, 3), List(3, 1, 2), List(3, 2, 1))
+ }
+ }
+
+ "The ListHelpers permuteWithSublists function (permuteAll method on a List object)" should {
+ "create a List of all the permutations of a given list" in {
+ List(1, 2, 3).permuteAll ===
+ List(
+ List(1, 2, 3), List(1, 3, 2), List(2, 3, 1),
+ List(2, 1, 3), List(3, 1, 2), List(3, 2, 1),
+ List(2, 3), List(3, 2), List(3, 1),
+ List(1, 3), List(1, 2), List(2, 1),
+ List(3), List(2), List(1))
+ }
+ }
+
+ "The ListHelpers" should {
+ "provide an or method on Lists returning the list itself if not empty or another list if it is empty" in {
+ List(1).or(List(2)) === List(1)
+ (Nil: List[Int]).or(List(2)) === List(2)
+ }
+ "provide a str method on Lists joining the toString value of all elements" in {
+ List("h", "e", "l", "l", "o").str === "hello"
+ }
+ "provide a comma method on Lists being an alias for mkString(\", \")" in {
+ List("hello", "world").comma === "hello, world"
+ }
+ "provide a join method on Lists being an alias for mkString" in {
+ List("hello", "world").join(", ") === "hello, world"
+ }
+ "provide a ? method return true iff the list is not empty" in {
+ List().? must beFalse
+ List(1).? must beTrue
+ }
+ "provide a replace method to replace one element of the list at a given position (0-based index)." +
+ " If the position is negative, the first element is replaced" in {
+ List(1, 2, 3).replace(1, 4) === List(1, 4, 3)
+ List(1, 2, 3).replace(4, 4) === List(1, 2, 3)
+ List(1, 2, 3).replace(-1, 4) === List(4, 2, 3)
+ }
+ }
+
+}
diff --git a/core/util/src/test/scala-3/net/liftweb/util/PCDataXmlParserSpec.scala b/core/util/src/test/scala-3/net/liftweb/util/PCDataXmlParserSpec.scala
new file mode 100644
index 000000000..4e12fcc96
--- /dev/null
+++ b/core/util/src/test/scala-3/net/liftweb/util/PCDataXmlParserSpec.scala
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2010-2011 WorldWide Conferencing, LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.liftweb
+package util
+
+import org.specs2.matcher.XmlMatchers
+import org.specs2.mutable.Specification
+
+
+/**
+ * Systems under specification for PCDataXmlParser.
+ */
+class PCDataXmlParserSpec extends Specification with XmlMatchers {
+ "PCDataXmlParser Specification".title
+val data1 = """
+
+
+dude
+
+
+"""
+
+val data2 = """
+
+
+dude
+
+
+"""
+
+val data3 = """
+
+
+
+Meow
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+"""
+
+
+ "PCDataMarkupParser" should {
+ "Parse a document with whitespace" in {
+ PCDataXmlParser(data1).openOrThrowException("Test") must ==/ (dude)
+ }
+
+ "Parse a document with doctype" in {
+ PCDataXmlParser(data2).openOrThrowException("Test") must ==/ (dude)
+ }
+
+ "Parse a document with xml and doctype" in {
+ PCDataXmlParser(data3).openOrThrowException("Test").apply(0).label === "html"
+ }
+
+ }
+
+}
+
diff --git a/core/util/src/test/scala-3/net/liftweb/util/PropsSpec.scala b/core/util/src/test/scala-3/net/liftweb/util/PropsSpec.scala
new file mode 100644
index 000000000..46018eeca
--- /dev/null
+++ b/core/util/src/test/scala-3/net/liftweb/util/PropsSpec.scala
@@ -0,0 +1,171 @@
+/*
+ * Copyright 2006-2012 WorldWide Conferencing, LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.liftweb
+package util
+
+import java.io.ByteArrayInputStream
+
+import org.specs2.mutable.Specification
+import org.specs2.specification.After
+
+import common._
+import Props.RunModes._
+
+class PropsSpec extends Specification {
+ "Props Specification".title
+
+ case class TestProps() extends Props
+
+ "Props" should {
+ "Detect test mode correctly" in {
+ TestProps().testMode === true
+ }
+
+ "Allow modification of whereToLook before run-mode is set" in {
+ val testProps = TestProps()
+ val originalWtl = testProps.whereToLook
+
+ var wasCalled = false
+ testProps.whereToLook = () => {
+ wasCalled = true
+
+ List(
+ ("test propsters", () => Full(new ByteArrayInputStream("test.prop=value".getBytes("UTF-8"))))
+ )
+ }
+
+ testProps.getInt("jetty.port") must beEqualTo(Empty)
+ testProps.get("test.prop") must beEqualTo(Full("value"))
+ wasCalled === true
+ }
+
+ "Allow modification of run-mode properties before the run-mode is set" in {
+ val testProps = TestProps()
+
+ val before = testProps.autoDetectRunModeFn.get
+ try {
+ testProps.runModeInitialised = false
+ testProps.autoDetectRunModeFn.allowModification === true
+ testProps.autoDetectRunModeFn.set(() => Test) === true
+ testProps.autoDetectRunModeFn.get must not(beEqualTo(before))
+ } finally {
+ testProps.autoDetectRunModeFn.set(before)
+ testProps.runModeInitialised = true
+ }
+ }
+
+ "Prohibit modification of run-mode properties when the run-mode is set" in {
+ val testProps = TestProps()
+
+ val before = testProps.autoDetectRunModeFn.get
+ testProps.mode // initialize run mode
+ testProps.autoDetectRunModeFn.allowModification === false
+ testProps.autoDetectRunModeFn.set(() => Test) === false
+ testProps.autoDetectRunModeFn.get === before
+ }
+
+ "Parse and cast to int" in {
+ TestProps().getInt("an.int") must beEqualTo(Full(42))
+ }
+
+ "Parse and cast to long" in {
+ TestProps().getLong("a.long") must beEqualTo(Full(9223372036854775807L))
+ }
+
+ "Parse and cast to boolean" in {
+ TestProps().getBool("a.boolean") must beEqualTo(Full(true))
+ }
+
+ "Prefer prepended properties to the test.default.props" in {
+ val testProps = TestProps()
+
+ testProps.prependProvider(Map("jetty.port" -> "8080"))
+ val port = testProps.getInt("jetty.port")
+
+ port must beEqualTo(Full(8080))
+ }
+
+ "Prefer prepended System.properties to the test.default.props" in {
+ val testProps = TestProps()
+
+ System.setProperty("omniauth.baseurl1", "http://google.com")
+
+ testProps.prependProvider(sys.props)
+ val baseurl = testProps.get("omniauth.baseurl1")
+
+ baseurl must beEqualTo(Full("http://google.com"))
+ }
+
+ "Read through to System.properties, correctly handling mutation" in {
+ val testProps = TestProps()
+
+ System.setProperty("omniauth.baseurl2", "http://google.com")
+ testProps.prependProvider(sys.props)
+ System.setProperty("omniauth.baseurl2", "http://ebay.com")
+ val baseurl = testProps.get("omniauth.baseurl2")
+
+ baseurl must beEqualTo(Full("http://ebay.com"))
+ }
+
+ "Find properties in appended maps when not defined in test.default.props" in {
+ val testProps = TestProps()
+
+ testProps.appendProvider(Map("new.prop" -> "new.value"))
+ val prop = testProps.get("new.prop")
+
+ prop must beEqualTo(Full("new.value"))
+ }
+
+ "Not interpolate values when no interpolator is given" in {
+ val port = TestProps().get("jetty.port")
+
+ port must beEqualTo(Full("${PORT}"))
+ }
+
+ "Interpolate values from the given interpolator" in {
+ val testProps = TestProps()
+
+ testProps.appendInterpolationValues(Map("PORT" -> "8080"))
+ val port = testProps.getInt("jetty.port")
+
+ port must beEqualTo(Full(8080))
+ }
+
+ "Interpolate multiple values in a string from the given interpolator" in {
+ val testProps = TestProps()
+
+ testProps.appendInterpolationValues(Map("DB_HOST" -> "localhost", "DB_PORT" -> "3306"))
+ val url = testProps.get("db.url")
+
+ url must beEqualTo(Full("jdbc:mysql://localhost:3306/MYDB"))
+ }
+
+ "Find properties in append for require()" in {
+ val testProps = TestProps()
+
+ testProps.appendProvider(Map("new.prop" -> "new.value"))
+ testProps.require("new.prop") === Nil
+ }
+
+ "Find properties in prepend for require()" in {
+ val testProps = TestProps()
+
+ testProps.prependProvider(Map("new.prop" -> "new.value"))
+ testProps.require("new.prop") === Nil
+ }
+ }
+}
diff --git a/core/util/src/test/scala-3/net/liftweb/util/ScheduleSpec.scala b/core/util/src/test/scala-3/net/liftweb/util/ScheduleSpec.scala
new file mode 100644
index 000000000..50aa99517
--- /dev/null
+++ b/core/util/src/test/scala-3/net/liftweb/util/ScheduleSpec.scala
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2007-2011 WorldWide Conferencing, LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.liftweb
+package util
+
+import org.specs2.mutable.Specification
+import org.specs2.specification.BeforeEach
+import org.specs2.execute.PendingUntilFixed
+
+import actor._
+import Helpers._
+
+
+/**
+ * Systems under specification for Lift Schedule.
+ */
+class ScheduleSpec extends Specification with PendingUntilFixed with PingedService with BeforeEach {
+ "Schedule Specification".title
+
+ def before = step(Schedule.restart)
+
+ "The Schedule object" should {
+ "provide a schedule method to ping an actor regularly" in {
+ Schedule.schedule(service, Alive, TimeSpan(10))
+ service.pinged must eventually(beTrue)
+ }
+ "honor multiple restarts" in {
+ Schedule.restart
+ Schedule.restart
+ Schedule.restart
+ Schedule.schedule(service, Alive, TimeSpan(10))
+ service.pinged must eventually(beTrue)
+ }
+ "honor shutdown followed by restart" in {
+ Schedule.shutdown()
+ Schedule.restart
+ Schedule.schedule(service, Alive, TimeSpan(10))
+ service.pinged must eventually(beTrue)
+ }
+ "not honor multiple shutdowns" in {
+ Schedule.shutdown()
+ Schedule.shutdown()
+// service.pinged must eventually(beFalse)
+ service.pinged must throwA[ActorPingException]
+ }.pendingUntilFixed
+ }
+
+}
+
+
+trait PingedService {
+ case object Alive
+ val service = new Service
+
+ class Service extends LiftActor {
+ @volatile var pinged = false
+ /*
+ def act() {
+ while (true) {
+ receive {
+ case Alive => {pinged = true; exit()}
+ }
+ }
+ }
+ */
+ protected def messageHandler = {
+ case Alive => {pinged = true /*; exit() */}
+ }
+ }
+}
diff --git a/core/util/src/test/scala-3/net/liftweb/util/SecurityHelpersSpec.scala b/core/util/src/test/scala-3/net/liftweb/util/SecurityHelpersSpec.scala
new file mode 100644
index 000000000..058ab9d13
--- /dev/null
+++ b/core/util/src/test/scala-3/net/liftweb/util/SecurityHelpersSpec.scala
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2006-2011 WorldWide Conferencing, LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package net.liftweb
+package util
+
+import org.xml.sax.SAXParseException
+
+import org.specs2.mutable.Specification
+
+import SecurityHelpers._
+
+/**
+ * Systems under specification for SecurityHelpers.
+ */
+class SecurityHelpersSpec extends Specification {
+ "SecurityHelpers Specification".title
+
+ "Security Helpers" should {
+ "not parse XML with a DOCTYPE" in {
+ secureXML.loadString("""
+
+ ]>
+ &xxe;"""
+ ) must throwA[SAXParseException]
+ }
+
+ "parse XML without a DOCTYPE" in {
+ secureXML.loadString("""
+ &
+ """).toString === "&"
+ }
+
+ "provide a randomLong method returning a random Long modulo a number" in {
+ randomLong(7L) must beLessThan(7L)
+ }
+ "provide a randomInt method returning a random Int modulo a number" in {
+ randomInt(7) must beLessThan(7)
+ }
+ "provide a shouldShow function always returning true only a given percentage of time, expressed as a Int between 0 and 100" in {
+ shouldShow(100) must beTrue
+ shouldShow(0) must beFalse
+ }
+ "provide a shouldShow function always returning true only a given percentage of time, expressed as a Double between 0 and 1.0" in {
+ shouldShow(1.0) must beTrue
+ shouldShow(0.0) must beFalse
+ }
+
+ /*
+ "provide makeBlowfishKey, blowfishEncrypt, blowfishDecrypt functions to encrypt/decrypt Strings with Blowfish keys" in {
+ val key = makeBlowfishKey
+ val encrypted = blowfishEncrypt("hello world", key)
+ encrypted must not(beEqualTo("hello world"))
+ blowfishDecrypt(encrypted, key) === "hello world"
+ }
+ */
+
+ "provide a md5 function to create a md5 digest from a string" in {
+ md5("hello") === "XUFAKrxLKna5cZ2REBfFkg=="
+ md5("hello") must not(beEqualTo(md5("hell0")))
+ }
+ "provide a hash function to create a SHA digest from a string" in {
+ hash("hello") === "qvTGHdzF6KLavt4PO0gs2a6pQ00="
+ hash("hello") must not(beEqualTo(hash("hell0")))
+ }
+ "provide a hash256 function to create a SHA-256 digest from a string" in {
+ hash256("hello") === "LPJNul+wow4m6DsqxbninhsWHlwfp0JecwQzYpOLmCQ="
+ hash256("hello") must not(beEqualTo(hash256("hell0")))
+ }
+ "provide a hex encoded SHA hash function" in {
+ hexDigest("hello".getBytes) === "aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d"
+ hexDigest("hello".getBytes) must not(beEqualTo(hexDigest("hell0".getBytes)))
+ }
+ "provide a hex encoded SHA-256 hash function" in {
+ hexDigest256("hello".getBytes) === "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
+ hexDigest256("hello".getBytes) must not(beEqualTo(hexDigest256("hell0".getBytes)))
+ }
+ }
+
+}
+
diff --git a/core/util/src/test/scala-3/net/liftweb/util/SoftReferenceCacheSpec.scala b/core/util/src/test/scala-3/net/liftweb/util/SoftReferenceCacheSpec.scala
new file mode 100644
index 000000000..52670f05a
--- /dev/null
+++ b/core/util/src/test/scala-3/net/liftweb/util/SoftReferenceCacheSpec.scala
@@ -0,0 +1,27 @@
+package net.liftweb.util
+
+import org.specs2.mutable._
+import net.liftweb.common._
+
+class SoftReferenceCacheSpec extends Specification {
+
+ sequential
+
+ object cache extends SoftReferenceCache[String, String](1)
+
+ "SoftReferenceCache " should {
+ "Accept additions" in {
+ cache += ("test" -> "test")
+ cache.keys.size() === 1
+ }
+ "Allow objects to be retrieved" in {
+ val cached = cache("test")
+ cached must beLike { case Full("test") => ok }
+ }
+ "Properly age out entries" in {
+ cache += ("test2" -> "test2")
+ cache("test") must beEqualTo(Empty)
+ }
+ }
+
+}
diff --git a/core/util/src/test/scala-3/net/liftweb/util/StringHelpersSpec.scala b/core/util/src/test/scala-3/net/liftweb/util/StringHelpersSpec.scala
new file mode 100644
index 000000000..cc59507e7
--- /dev/null
+++ b/core/util/src/test/scala-3/net/liftweb/util/StringHelpersSpec.scala
@@ -0,0 +1,351 @@
+/*
+ * Copyright 2007-2011 WorldWide Conferencing, LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.liftweb
+package util
+
+import org.specs2.mutable.Specification
+import org.specs2.ScalaCheck
+import org.scalacheck.Gen._
+import org.scalacheck.Prop.forAll
+
+import StringHelpers._
+
+
+/**
+ * Systems under specification for StringHelpers.
+ */
+class StringHelpersSpec extends Specification with ScalaCheck with StringGen {
+ "StringHelpers Specification".title
+
+ "The snakify function" should {
+ "replace upper case with underscore" in {
+ snakify("MyCamelCase") === "my_camel_case"
+ snakify("CamelCase") === "camel_case"
+ snakify("Camel") === "camel"
+ snakify("MyCamel12Case") === "my_camel12_case"
+ snakify("CamelCase12") === "camel_case12"
+ snakify("Camel12") === "camel12"
+ }
+
+ "not modify existing snake case strings" in {
+ snakify("my_snake_case") === "my_snake_case"
+ snakify("snake") === "snake"
+ }
+
+ "handle abbeviations" in {
+ snakify("HTML") === "html"
+ snakify("HTMLEditor") === "html_editor"
+ snakify("EditorTOC") === "editor_toc"
+ snakify("HTMLEditorTOC") === "html_editor_toc"
+
+ snakify("HTML5") === "html5"
+ snakify("HTML5Editor") === "html5_editor"
+ snakify("Editor2TOC") === "editor2_toc"
+ snakify("HTML5Editor2TOC") === "html5_editor2_toc"
+ }
+ }
+
+ "The camelify function" should {
+ "CamelCase a name which is underscored, removing each underscore and capitalizing the next letter" in {
+ def previousCharacterIsUnderscore(name: String, i: Int) = i > 1 && name.charAt(i - 1) == '_'
+ def underscoresNumber(name: String, i: Int) = if (i == 0) 0 else name.substring(0, i).toList.count(_ == '_')
+ def correspondingIndexInCamelCase(name: String, i: Int) = i - underscoresNumber(name, i)
+ def correspondingCharInCamelCase(name: String, i: Int): Char = camelify(name).charAt(correspondingIndexInCamelCase(name, i))
+
+ val doesntContainUnderscores = forAll(underscoredStrings){ ((name: String) => !camelify(name).contains("_")) }
+ val isCamelCased = forAll(underscoredStrings) ((name: String) => {
+ name.forall(_ == '_') && camelify(name).isEmpty ||
+ name.toList.zipWithIndex.forall { case (c, i) =>
+ c == '_' ||
+ correspondingIndexInCamelCase(name, i) == 0 && correspondingCharInCamelCase(name, i) == c.toUpper ||
+ !previousCharacterIsUnderscore(name, i) && correspondingCharInCamelCase(name, i) == c ||
+ previousCharacterIsUnderscore(name, i) && correspondingCharInCamelCase(name, i) == c.toUpper
+ }
+ })
+ (doesntContainUnderscores && isCamelCased)
+ }
+ "return an empty string if given null" in {
+ camelify(null) === ""
+ }
+ "leave a CamelCased name untouched" in {
+ forAll(camelCasedStrings){ (name: String) => camelify(name) == name }
+ }
+ }
+
+ "The camelifyMethod function" should {
+ "camelCase a name with the first letter being lower cased" in {
+ forAll(underscoredStrings){
+ (name: String) =>
+ camelify(name).isEmpty && camelifyMethod(name).isEmpty ||
+ camelifyMethod(name).toList.head.isLower && camelify(name) == camelifyMethod(name).capitalize
+ }
+ }
+ }
+
+ "SuperListString" should {
+ """allow "foo" / "bar" """ in {
+ ("foo" / "bar") === List("foo", "bar")
+ }
+
+ """allow "foo" / "bar" / "baz" """ in {
+ ("foo" / "bar" / "baz") === List("foo", "bar", "baz")
+ }
+ }
+
+ "The StringHelpers processString function" should {
+ "replace groups found in a string surrounded by <%= ... %> by their corresponding value in a map" in {
+ processString("<%=hello%>", Map("hello" -> "bonjour")) === "bonjour"
+ }
+ "replace groups found in several strings surrounded by <%= ... %> by their corresponding value in a map" in {
+ processString("<%=hello%> <%=world%>", Map("hello" -> "bonjour", "world" -> "monde")) === "bonjour monde"
+ }
+ "not replace the group if it starts with %" in {
+ processString("<%=%hello%>", Map("hello" -> "bonjour")) === "<%=%hello%>"
+ }
+ "throw an exception if no correspondance is found" in {
+ processString("<%=hello%>", Map("hallo" -> "bonjour")) must throwA[Exception]
+ }
+ }
+
+ "The StringHelpers capify function" should {
+ "capitalize a word" in {
+ capify("hello") === "Hello"
+ }
+ "capitalize the first letters of 2 words" in {
+ capify("hello world") === "Hello World"
+ }
+ "capitalize the first letters of 2 words separated by an underscore" in {
+ capify("hello_world") === "Hello_World"
+ }
+ "capitalize the second character in a word if the first is a digit" in {
+ capify("hello 1world") === "Hello 1World"
+ }
+ "capitalize the third character in a word if the first 2 are digits" in {
+ capify("hello 12world") === "Hello 12World"
+ }
+ "suppress symbols placed in front of words and capitalize the words" in {
+ capify("@hello $world") === "Hello World"
+ }
+ "remove letters in the word if there are more than 250 digits in front of it" in {
+ capify("1" * 250 + "hello") === "1" * 250
+ }
+ }
+ "The StringHelpers clean function" should {
+ "return an empty string if the input is null" in {
+ clean(null) === ""
+ }
+ "remove every character which is not a-zA-Z0-9_" in {
+ clean(" He@llo Wor+ld_!") === "HelloWorld_"
+ }
+ }
+ "The StringHelpers randomString" should {
+ "return an empty string if size is 0" in {
+ randomString(0) === ""
+ }
+ /*
+ "return a string of size n" in {
+ randomString(10).toSeq must haveSize(10)
+ }
+ */
+
+ "return only capital letters and digits" in {
+ randomString(10) must beMatching("[A-Z0-9]*")
+ }
+ }
+ "The StringHelpers escChar" should {
+ "return the unicode value of a character as a string starting with \\u" in {
+ escChar('{') === "\\u007b"
+ escChar('A') === "\\u0041"
+ }
+ }
+ "The StringHelpers splitColonPair" should {
+ "split a string separated by a dot in 2 parts" in {
+ splitColonPair("a.b", "1", "2") === ("a", "b")
+ }
+ "split a string separated by a dot in 2 trimmed parts" in {
+ splitColonPair(" a . b ", "1", "2") === ("a", "b")
+ }
+ "split a string separated by a column in 2 parts" in {
+ splitColonPair("a:b", "1", "2") === ("a", "b")
+ }
+ "split a string according to the dot prioritarily" in {
+ splitColonPair("a.b:c", "1", "2") === ("a", "b:c")
+ }
+ "split a string according to first the dot if there are several ones and removing everything after the next dot" in {
+ splitColonPair("a.b.c", "1", "2") === ("a", "b")
+ splitColonPair("a.b.c.d", "1", "2") === ("a", "b")
+ }
+ "use a default value for the second part if there is nothing to split" in {
+ splitColonPair("abc", "1", "2") === ("abc", "2")
+ }
+ "use null for the first part if the input string is null" in {
+ splitColonPair(null, "1", "2") === ("", "2")
+ }
+ "use a default value for the first part if the input string is empty" in {
+ splitColonPair("", "1", "2") === ("1", "2")
+ }
+ }
+ "The StringHelpers parseNumber function" should {
+ "return a Long corresponding to the value of a string with digits" in {
+ parseNumber("1234") === 1234L
+ }
+ "return a positive Long value if the string starts with +" in {
+ parseNumber("+1234") === 1234L
+ }
+ "return a positive Long value if the string starts with -" in {
+ parseNumber("-1234") === -1234L
+ }
+ "return 0 if the string is null" in {
+ parseNumber(null) === 0L
+ }
+ "return 0 if the string is empty" in {
+ parseNumber("") === 0L
+ }
+ "return 0 if the string can't be parsed" in {
+ parseNumber("string") === 0L
+ }
+ }
+ "The SuperString class roboSplit method" should {
+ "split a string according to a separator" in {
+ "hello".roboSplit("ll") === List("he", "o")
+ }
+ "split a string according to a separator - 2" in {
+ "hAeAlAlAo".roboSplit("A") === List("h", "e", "l", "l", "o")
+ }
+ "split a string into trimmed parts" in {
+ "hello . world ".roboSplit("\\.") === List("hello", "world")
+ }
+ "split a string into trimmed parts whose length is > 0" in {
+ "hello . . world ".roboSplit("\\.") === List("hello", "world")
+ }
+ "split a string according to a separator. The separator is always interpreted as a regular expression" in {
+ "hello".roboSplit("(l)+") === List("he", "o")
+ }
+ "return a list containing the string if the separator is not found" in {
+ "hello".roboSplit("tt") === List("hello")
+ }
+ "return an empty list if the string is null" in {
+ (null: String).roboSplit("a") === List()
+ }
+ "return a list containing the string if the string is empty" in {
+ "".roboSplit("a") === List()
+ }
+ }
+
+ "The SuperString class charSplit method" should {
+ "split a string according to a separator" in {
+ "hello".charSplit('l') === List("he", "o")
+ }
+ "split a string according to a separator - 2" in {
+ "hAeAlAlAo".charSplit('A') === List("h", "e", "l", "l", "o")
+ }
+ "split a string" in {
+ "hello . world ".charSplit('.') === List("hello ", " world ")
+ }
+ "split a string into parts" in {
+ "hello .. world ".charSplit('.') === List("hello ", " world ")
+ }
+ "return an empty list if the string is null" in {
+ (null: String).charSplit('a') === List()
+ }
+ "return a list containing the string if the string is empty" in {
+ "".charSplit('a') === List()
+ }
+ }
+
+
+ "The SuperString class splitAt method" should {
+ "split a string according to a separator and return a List containing a pair with the 2 parts" in {
+ stringToSuper("hello").splitAt("ll") === List(("he", "o"))
+ }
+ "split a string according to a separator and return an empty List if no separator is found" in {
+ stringToSuper("hello").splitAt("tt") === Nil
+ }
+ "split a string according to a separator and return an empty List if the string is null" in {
+ stringToSuper(null: String).splitAt("tt") === Nil
+ }
+ }
+ "The SuperString class encJs method" should {
+ "encode a string replacing backslash with its unicode value" in {
+ "\\hello".encJs === "\"\\u005chello\""
+ }
+ "encode a string replacing the quote character with its unicode value" in {
+ "h'ello".encJs === "\"h\\u0027ello\""
+ }
+ "encode a string adding a quote before and a quote after the string" in {
+ "hello".encJs === "\"hello\""
+ }
+ "encode a string replacing non-ASCII characters by their unicode value" in {
+ "ni\u00f1a".encJs === "\"ni\\u00f1a\""
+ }
+ "return the string \"null\" if the input string is null" in {
+ (null: String).encJs === "null"
+ }
+ }
+ "The SuperString class commafy method" should {
+ "add a comma before the last 3 characters of the input string" in {
+ "hello".commafy === "he,llo"
+ }
+ "add nothing if the input string is less than 4 characters" in {
+ "hel".commafy === "hel"
+ }
+ "return null if the input string is null" in {
+ (null: String).commafy must beNull
+ }
+ }
+ "The blankForNull method" should {
+ "return the empty String for a given null String" in {
+ StringHelpers.blankForNull(null) === ""
+ }
+ "return the given String for a given not-null String" in {
+ StringHelpers.blankForNull("x") === "x"
+ }
+ }
+ "The emptyForBlank method" should {
+ import net.liftweb.common._
+ "return Empty for a given null String" in {
+ StringHelpers.emptyForBlank(null) must beEqualTo(Empty)
+ }
+ "return Empty for a given a blank String" in {
+ StringHelpers.emptyForBlank("") must beEqualTo(Empty)
+ }
+ "return Empty for a String of spaces" in {
+ StringHelpers.emptyForBlank(" ") must beEqualTo(Empty)
+ }
+ "return the trim'ed String for a given not-null String" in {
+ StringHelpers.emptyForBlank(" x ") must beEqualTo(Full("x"))
+ }
+ }
+}
+
+
+trait StringGen {
+ val underscoredStrings =
+ for {
+ length <- choose(0, 4)
+ s <- listOfN(length, frequency((3, alphaChar), (1, oneOf(List('_')))))
+ } yield s.mkString
+
+ val camelCasedStrings =
+ for {
+ length <- choose(0, 4)
+ firstLetter <- alphaNumChar.map(_.toUpper)
+ string <- listOfN(length, frequency((3, alphaNumChar.map(_.toLower)),
+ (1, alphaNumChar.map(_.toUpper))))
+ } yield (firstLetter :: string).mkString
+}
+
diff --git a/core/util/src/test/scala-3/net/liftweb/util/TimeHelpersSpec.scala b/core/util/src/test/scala-3/net/liftweb/util/TimeHelpersSpec.scala
new file mode 100644
index 000000000..0a91fd332
--- /dev/null
+++ b/core/util/src/test/scala-3/net/liftweb/util/TimeHelpersSpec.scala
@@ -0,0 +1,249 @@
+/*
+ * Copyright 2006-2015 WorldWide Conferencing, LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.liftweb
+package util
+
+import java.util.{Calendar, Date, TimeZone}
+
+import net.liftweb.common._
+import net.liftweb.util.TimeHelpers._
+import org.joda.time.{Period, DateTimeZone, DateTime}
+import org.scalacheck.Gen._
+import org.scalacheck.Prop._
+import org.specs2.ScalaCheck
+import org.specs2.execute.AsResult
+import org.specs2.matcher.MatcherImplicits
+import org.specs2.mutable.Specification
+import org.specs2.specification.Around
+
+/**
+ * Systems under specification for TimeHelpers.
+ */
+class TimeHelpersSpec extends Specification with ScalaCheck with TimeAmountsGen {
+ "TimeHelpers Specification".title
+
+ "A TimeSpan" can {
+ "be created from a number of milliseconds" in forAllTimeZones {
+ TimeSpan(3000) === TimeSpan(3 * 1000)
+ }
+ "be created from a number of seconds" in forAllTimeZones {
+ 3.seconds === TimeSpan(3 * 1000)
+ }
+ "be created from a number of minutes" in forAllTimeZones {
+ 3.minutes === TimeSpan(3 * 60 * 1000)
+ }
+ "be created from a number of hours" in forAllTimeZones {
+ 3.hours === TimeSpan(3 * 60 * 60 * 1000)
+ }
+ "be created from a number of days" in forAllTimeZones {
+ 3.days === TimeSpan(3 * 24 * 60 * 60 * 1000)
+ }
+ "be created from a number of weeks" in forAllTimeZones {
+ 3.weeks === TimeSpan(3 * 7 * 24 * 60 * 60 * 1000)
+ }
+ "be created from a number of months" in forAllTimeZones {
+ 3.months must beEqualTo(Period.months(3))
+ }
+ "be created from a number of years" in forAllTimeZones {
+ 3.years must beEqualTo(Period.years(3))
+ }
+ "be converted implicitly to a date starting from the epoch time" in forAllTimeZones {
+ 3.seconds.after(new Date(0)) must beTrue
+ }
+ "be converted to a date starting from the epoch time, using the date method" in forAllTimeZones {
+ 3.seconds.after(new Date(0)) must beTrue
+ }
+ "be compared to another TimeSpan" in forAllTimeZones {
+ 3.seconds === 3.seconds
+ 3.seconds must not(beEqualTo(2.seconds))
+ }
+ "be compared to another object" in forAllTimeZones {
+ 3.seconds must not(beEqualTo("string"))
+ }
+ }
+
+ "A TimeSpan" should {
+ "return a new TimeSpan representing the sum of the 2 times when added with another TimeSpan" in forAllTimeZones {
+ 3.seconds + 3.seconds === 6.seconds
+ }
+ "return a new TimeSpan representing the difference of the 2 times when substracted with another TimeSpan" in forAllTimeZones {
+ 3.seconds - 4.seconds === (-1).seconds
+ }
+ "have a toString method returning the relevant number of weeks, days, hours, minutes, seconds, millis" in forAllTimeZones {
+ val conversionIsOk = forAll(timeAmounts)((t: TimeAmounts) => { val (timeSpanToString, timeSpanAmounts) = t
+ timeSpanAmounts forall { case (amount, unit) =>
+ amount >= 1 &&
+ timeSpanToString.contains(amount.toString) || true }
+ })
+ val timeSpanStringIsPluralized = forAll(timeAmounts)((t: TimeAmounts) => { val (timeSpanToString, timeSpanAmounts) = t
+ timeSpanAmounts forall { case (amount, unit) =>
+ amount > 1 && timeSpanToString.contains(unit + "s") ||
+ amount == 1 && timeSpanToString.contains(unit) ||
+ amount == 0 && !timeSpanToString.contains(unit)
+ }
+ })
+ conversionIsOk && timeSpanStringIsPluralized
+ }
+ }
+
+ "the TimeHelpers" should {
+ "provide a 'seconds' function transforming a number of seconds into millis" in forAllTimeZones {
+ seconds(3) === 3 * 1000
+ }
+ "provide a 'minutes' function transforming a number of minutes into millis" in forAllTimeZones {
+ minutes(3) === 3 * 60 * 1000
+ }
+ "provide a 'hours' function transforming a number of hours into milliss" in forAllTimeZones {
+ hours(3) === 3 * 60 * 60 * 1000
+ }
+ "provide a 'days' function transforming a number of days into millis" in forAllTimeZones {
+ days(3) === 3 * 24 * 60 * 60 * 1000
+ }
+ "provide a 'weeks' function transforming a number of weeks into millis" in forAllTimeZones {
+ weeks(3) === 3 * 7 * 24 * 60 * 60 * 1000
+ }
+ "provide a noTime function on Date objects to transform a date into a date at the same day but at 00:00" in forAllTimeZones {
+ hourFormat(now.noTime) === "00:00:00"
+ }
+
+ "provide a day function returning the day of month corresponding to a given date (relative to UTC)" in forAllTimeZones {
+ day(today.setTimezone(utc).setDay(3).getTime) === 3
+ }
+ "provide a month function returning the month corresponding to a given date" in forAllTimeZones {
+ month(today.setTimezone(utc).setMonth(4).getTime) === 4
+ }
+ "provide a year function returning the year corresponding to a given date" in forAllTimeZones {
+ year(today.setTimezone(utc).setYear(2008).getTime) === 2008
+ }
+ "provide a millisToDays function returning the number of days since the epoch time" in forAllTimeZones {
+ millisToDays(new Date(0).getTime) === 0
+ millisToDays(today.setYear(1970).setMonth(0).setDay(1).getTime.getTime) === 0 // the epoch time
+ // on the 3rd day after the epoch time, 2 days are passed
+ millisToDays(today.setTimezone(utc).setYear(1970).setMonth(0).setDay(3).getTime.getTime) === 2
+ }
+ "provide a daysSinceEpoch function returning the number of days since the epoch time" in forAllTimeZones {
+ daysSinceEpoch === millisToDays(now.getTime)
+ }
+ "provide a time function creating a new Date object from a number of millis" in forAllTimeZones {
+ time(1000) === new Date(1000)
+ }
+ "provide a calcTime function returning the time taken to evaluate a block in millis and the block's result" in forAllTimeZones {
+ val (time, result) = calcTime((1 to 10).reduceLeft[Int](_ + _))
+ time.toInt must beCloseTo(0, 1000) // it should take less than 1 second!
+ result === 55
+ }
+
+ "provide a hourFormat function to format the time of a date object" in forAllTimeZones {
+ hourFormat(Calendar.getInstance(utc).noTime.getTime) === "00:00:00"
+ }
+
+ "provide a formattedDateNow function to format todays date" in forAllTimeZones {
+ formattedDateNow must beMatching("\\d\\d\\d\\d/\\d\\d/\\d\\d")
+ }
+ "provide a formattedTimeNow function to format now's time with the TimeZone" in forAllTimeZones {
+ val regex = "\\d\\d:\\d\\d (....?.?|GMT((\\+|\\-)\\d\\d:\\d\\d)?)"
+ "10:00 CEST" must beMatching(regex)
+ "10:00 GMT+02:00" must beMatching(regex)
+ "10:00 GMT" must beMatching(regex)
+ "10:00 XXX" must beMatching(regex)
+ formattedTimeNow must beMatching(regex)
+ }
+
+ "provide a parseInternetDate function to parse a string formatted using the internet format" in forAllTimeZones {
+ parseInternetDate(internetDateFormatter.format(now)).getTime.toLong must beCloseTo(now.getTime.toLong, 1000L)
+ }
+ "provide a parseInternetDate function returning new Date(0) if the input date cant be parsed" in forAllTimeZones {
+ parseInternetDate("unparsable") === new Date(0)
+ }
+ "provide a toInternetDate function formatting a date to the internet format" in forAllTimeZones {
+ toInternetDate(now) must beMatching("..., \\d* ... \\d\\d\\d\\d \\d\\d:\\d\\d:\\d\\d .*")
+ }
+ "provide a toDate returning a Full(date) from many kinds of objects" in forAllTimeZones {
+ val d = now
+ List(null, Nil, None, Failure("", Empty, Empty)) forall { toDate(_) == Empty } must beTrue
+ List(Full(d), Some(d), List(d)) forall { toDate(_) == Full(d) } must beTrue
+
+ toDate(internetDateFormatter.format(d)) must beLike {
+ case Full(converted) =>
+ converted.getTime.toLong must beCloseTo(d.getTime.toLong, 1000L)
+ }
+ }
+ }
+
+ "The Calendar class" should {
+ "have a setDay method setting the day of month and returning the updated Calendar" in forAllTimeZones {
+ day(today.setTimezone(utc).setDay(1).getTime) === 1
+ }
+ "have a setMonth method setting the month and returning the updated Calendar" in forAllTimeZones {
+ month(today.setTimezone(utc).setMonth(0).getTime) === 0
+ }
+ "have a setYear method setting the year and returning the updated Calendar" in forAllTimeZones {
+ year(today.setTimezone(utc).setYear(2008).getTime) === 2008
+ }
+ "have a setTimezone method to setting the time zone and returning the updated Calendar" in forAllTimeZones {
+ today.setTimezone(utc).getTimeZone === utc
+ }
+ "have a noTime method to setting the time to 00:00:00 and returning the updated Calendar" in forAllTimeZones {
+ hourFormat(today.noTime.getTime) === "00:00:00"
+ }
+ }
+}
+
+object forAllTimeZones extends Around {
+ import MatcherImplicits._
+
+ override def around[T: AsResult](f: => T) = synchronized {
+ import scala.jdk.CollectionConverters._
+ // setDefault is on static context so tests should be sequenced
+ // some timezones for java (used in formatters) and for Joda (other computations) has other offset
+ val commonJavaAndJodaTimeZones = (TimeZone.getAvailableIDs.toSet & DateTimeZone.getAvailableIDs.asScala.toSet).filter { timeZoneId =>
+ TimeZone.getTimeZone(timeZoneId).getOffset(millis) == DateTimeZone.forID(timeZoneId).getOffset(millis)
+ }
+ val tzBefore = TimeZone.getDefault
+ val dtzBefore = DateTimeZone.getDefault
+ try {
+ forall(commonJavaAndJodaTimeZones) { timeZoneId =>
+ TimeZone.setDefault(TimeZone.getTimeZone(timeZoneId))
+ DateTimeZone.setDefault(DateTimeZone.forID(timeZoneId))
+ f
+ }
+ } finally {
+ TimeZone.setDefault(tzBefore)
+ DateTimeZone.setDefault(dtzBefore)
+ }
+ }
+}
+
+
+trait TimeAmountsGen {
+
+ type TimeAmounts = (String, List[(Int, String)])
+
+ val timeAmounts =
+ for {
+ w <- choose(0, 2)
+ d <- choose(0, 6)
+ h <- choose(0, 23)
+ m <- choose(0, 59)
+ s <- choose(0, 59)
+ ml <- choose(0, 999)
+ }
+ yield (
+ TimeSpan(weeks(w) + days(d) + hours(h) + minutes(m) + seconds(s) + ml).toString,
+ (w, "week") :: (d, "day") :: (h, "hour") :: (m, "minute") :: (s, "second") :: (ml, "milli") :: Nil
+ )
+}
diff --git a/core/util/src/test/scala-3/net/liftweb/util/ToHeadSpec.scala b/core/util/src/test/scala-3/net/liftweb/util/ToHeadSpec.scala
new file mode 100644
index 000000000..7f64091f3
--- /dev/null
+++ b/core/util/src/test/scala-3/net/liftweb/util/ToHeadSpec.scala
@@ -0,0 +1,146 @@
+/*
+ * Copyright 2007-2011 WorldWide Conferencing, LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.liftweb
+package util
+
+import org.specs2.matcher.XmlMatchers
+import org.specs2.mutable.Specification
+
+import common._
+import ControlHelpers._
+import HeadHelper._
+import Helpers.secureXML
+
+/**
+ * Systems under specification for ToHead.
+ */
+class ToHeadSpec extends Specification with XmlMatchers {
+ "ToHead Specification".title
+
+ "lift merger" should {
+ "merge /html/body//head into existing /html/head section" in {
+ val susfiles = for {
+ act <- tryo(getClass.getResource("ToHeadSpec.actual1.html")).filter(_ ne null)
+ exp <- tryo(getClass.getResource("ToHeadSpec.expected1.html")).filter(_ ne null)
+ } yield (act, exp)
+
+ susfiles must beLike {
+ case Full(sus) =>
+ val actual = secureXML.load(sus._1)
+ val expected = secureXML.load(sus._2)
+ mergeToHtmlHead(actual).toString.replaceAll("\\s", "") ===
+ (expected.toString.replaceAll("\\s", ""))
+ }
+ }
+
+ "merge from real example" in {
+ val susfiles = for {
+ act <- tryo(getClass.getResource("ToHeadSpec.actual2.html")).filter(_ ne null)
+ exp <- tryo(getClass.getResource("ToHeadSpec.expected2.html")).filter(_ ne null)
+ } yield (act, exp)
+
+ susfiles must beLike {
+ case Full(sus) =>
+ val actual = secureXML.load(sus._1)
+ val expected = secureXML.load(sus._2)
+ mergeToHtmlHead(actual) must ==/(expected)
+ }
+ }
+
+ "merge into a new head if not previously exist" in {
+ val susfiles = for {
+ act <- tryo(getClass.getResource("ToHeadSpec.actual3.html")).filter(_ ne null)
+ exp <- tryo(getClass.getResource("ToHeadSpec.expected3.html")).filter(_ ne null)
+ } yield (act, exp)
+
+ susfiles must beLike {
+ case Full(sus) =>
+ val actual = secureXML.load(sus._1)
+ val expected = secureXML.load(sus._2)
+ mergeToHtmlHead(actual).toString.replaceAll("\\s", "") ===
+ (expected.toString.replaceAll("\\s", ""))
+ }
+ }
+ }
+
+ /*
+ "lift head cleaner" should {
+ "remove duplicate title tag" >> {
+ val actual = (hellohello2hello3)
+
+ val expected = (hello)
+
+ HeadHelper.cleanHead(actual) must beEqualToIgnoringSpace(expected)
+ }
+ "remove script tag with same id as previous script tag" >> {
+ val invariant = ()
+ HeadHelper.cleanHead(invariant) must beEqualToIgnoringSpace(invariant)
+
+ val actual = ()
+ val expected = ()
+ HeadHelper.cleanHead(actual) must beEqualToIgnoringSpace(expected)
+ }
+ "remove script tag with src attributes if src attributes are equals to previous script" >> {
+ val actual = ()
+ val expected = ()
+ HeadHelper.cleanHead(actual) must beEqualToIgnoringSpace(expected)
+
+ val actual2 = ()
+ val expected2 = ()
+ HeadHelper.cleanHead(actual2) must beEqualToIgnoringSpace(expected2)
+ }
+ "remove script tag if content are equals to previous script (need to trim each line ?)" >> {
+ val actual = ()
+ val expected = ()
+ HeadHelper.cleanHead(actual) must beEqualToIgnoringSpace(expected)
+ }
+ "remove link to css with same id as previous link tag" >> {
+ val invariant = ()
+ HeadHelper.cleanHead(invariant) must beEqualToIgnoringSpace(invariant)
+
+ val actual = ()
+ val expected = ()
+ HeadHelper.cleanHead(actual) must beEqualToIgnoringSpace(expected)
+ }
+ "remove link tag with href attributes if href attributes are equals to previous link" >> {
+ val invariant = ()
+ HeadHelper.cleanHead(invariant) must beEqualToIgnoringSpace(invariant)
+
+ val actual = ()
+ val expected = ()
+ HeadHelper.cleanHead(actual) must beEqualToIgnoringSpace(expected)
+ }
+ "remove style tag with same id as previous style tag" >> {
+ val invariant = ()
+ HeadHelper.cleanHead(invariant) must beEqualToIgnoringSpace(invariant)
+
+ val actual = ()
+ val expected = ()
+ HeadHelper.cleanHead(actual) must beEqualToIgnoringSpace(expected)
+ }
+ "remove style tag if content are equals to previous style (need to trim each line ?)" >> {
+ val invariant = ()
+ HeadHelper.cleanHead(invariant) must beEqualToIgnoringSpace(invariant)
+
+ val actual = ()
+ val expected = ()
+ HeadHelper.cleanHead(actual) must beEqualToIgnoringSpace(expected)
+ }
+ }
+*/
+}
+
diff --git a/core/util/src/test/scala-3/net/liftweb/util/VCardParserSpec.scala b/core/util/src/test/scala-3/net/liftweb/util/VCardParserSpec.scala
new file mode 100644
index 000000000..88f94590c
--- /dev/null
+++ b/core/util/src/test/scala-3/net/liftweb/util/VCardParserSpec.scala
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2008-2011 WorldWide Conferencing, LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.liftweb
+package util
+
+import org.specs2.matcher.XmlMatchers
+import org.specs2.mutable.Specification
+
+
+/**
+ * Systems under specification for VCardParser.
+ */
+class VCardParserSpec extends Specification with XmlMatchers {
+ "VCardParser Specification".title
+
+ val vcard =
+ """BEGIN:VCARD
+ |VERSION:2.1
+ |N:Gump;Forrest
+ |FN:Forrest Gump
+ |ORG:Bubba Gump Shrimp Co.
+ |TITLE:Shrimp Man
+ |TEL;WORK;VOICE:(111) 555-1212
+ |TEL;HOME;VOICE:(404) 555-1212
+ |END:VCARD""".stripMargin
+
+ "VCard" should {
+ "parse a basic VCard (2.1) correctly" in {
+ val list = VCardParser.parse(vcard)
+ list must beLike {
+ case Left(l) => {
+ import VCardParser._
+ l ===
+ List(
+ VCardEntry(VCardKey("BEGIN", Nil), List("VCARD")),
+ VCardEntry(VCardKey("VERSION", Nil), List("2.1")),
+ VCardEntry(VCardKey("N", Nil), List("Gump", "Forrest")),
+ VCardEntry(VCardKey("FN", Nil), List("Forrest Gump")),
+ VCardEntry(VCardKey("ORG", Nil), List("Bubba Gump Shrimp Co.")),
+ VCardEntry(VCardKey("TITLE", Nil), List("Shrimp Man")),
+ VCardEntry(VCardKey("TEL", List(("WORK", ""), ("VOICE", ""))), List("(111) 555-1212")),
+ VCardEntry(VCardKey("TEL", List(("HOME", ""), ("VOICE", ""))), List("(404) 555-1212")),
+ VCardEntry(VCardKey("END", Nil), List("VCARD")))
+ }
+ }
+ }
+
+ "parse a basic Apple VCard (3.0) correctly" in {
+
+ val appleIOS9DavidTaylorVCard3 =
+ """BEGIN:VCARD
+ |VERSION:3.0
+ |PRODID:-//Apple Inc.//iPhone OS 9.3//EN
+ |N:Taylor;David;;;
+ |FN: David Taylor
+ |TEL;type=HOME;type=VOICE;type=pref:555-610-6679
+ |item1.ADR;type=HOME;type=pref:;;1747 Steuart Street;Tiburon;CA;94920;USA
+ |item1.X-ABADR:us
+ |BDAY:1998-06-15
+ |END:VCARD""".stripMargin
+
+ val list = VCardParser.parse(appleIOS9DavidTaylorVCard3)
+ list must beLike {
+ case Left(l) => {
+ import VCardParser._
+ l ===
+ List(
+ VCardEntry(VCardKey("BEGIN", Nil), List("VCARD")),
+ VCardEntry(VCardKey("VERSION", Nil), List("3.0")),
+ VCardEntry(VCardKey("PRODID", Nil),List("-//Apple Inc.//iPhone OS 9.3//EN")),
+ VCardEntry(VCardKey("N", Nil), List("Taylor", "David", "", "", "")),
+ VCardEntry(VCardKey("FN", Nil), List(" David Taylor")),
+ VCardEntry(VCardKey("TEL", List(("type", "HOME"), ("type", "VOICE"), ("type", "pref"))), List("555-610-6679")),
+ VCardEntry(VCardKey("ADR", List(("type","HOME"), ("type","pref"))),List("", "", "1747 Steuart Street", "Tiburon", "CA", "94920", "USA")),
+ VCardEntry(VCardKey("X-ABADR",Nil),List("us")),
+ VCardEntry(VCardKey("BDAY",Nil),List("1998-06-15")),
+ VCardEntry(VCardKey("END", Nil), List("VCARD")))
+ }
+ }
+
+ }
+
+ "parse a more complex Apple VCard (3.0) correctly" in {
+
+ val appleIOS9JohnAppleseedVCard3 =
+ """BEGIN:VCARD
+ |VERSION:3.0
+ |PRODID:-//Apple Inc.//iPhone OS 9.3//EN
+ |N:Appleseed;John;;;
+ |FN: John Appleseed
+ |EMAIL;type=INTERNET;type=WORK;type=pref:John-Appleseed@mac.com
+ |TEL;type=CELL;type=VOICE;type=pref:888-555-5512
+ |TEL;type=HOME;type=VOICE:888-555-1212
+ |item1.ADR;type=WORK;type=pref:;;3494 Kuhl Avenue;Atlanta;GA;30303;USA
+ |item1.X-ABADR:ca
+ |item2.ADR;type=HOME:;;1234 Laurel Street;Atlanta;GA;30303;USA
+ |item2.X-ABADR:us
+ |BDAY:1980-06-22
+ |END:VCARD""".stripMargin
+
+ val list = VCardParser.parse(appleIOS9JohnAppleseedVCard3)
+ list must beLike {
+ case Left(l) => {
+ import VCardParser._
+ l ===
+ List(
+ VCardEntry(VCardKey("BEGIN", Nil), List("VCARD")),
+ VCardEntry(VCardKey("VERSION", Nil), List("3.0")),
+ VCardEntry(VCardKey("PRODID", Nil),List("-//Apple Inc.//iPhone OS 9.3//EN")),
+ VCardEntry(VCardKey("N", Nil), List("Appleseed", "John", "", "", "")),
+ VCardEntry(VCardKey("FN", Nil), List(" John Appleseed")),
+ VCardEntry(VCardKey("EMAIL", List(("type","INTERNET"), ("type","WORK"), ("type","pref"))),List("John-Appleseed@mac.com")),
+ VCardEntry(VCardKey("TEL", List(("type", "CELL"), ("type", "VOICE"), ("type", "pref"))), List("888-555-5512")),
+ VCardEntry(VCardKey("TEL", List(("type", "HOME"), ("type", "VOICE"))), List("888-555-1212")),
+ VCardEntry(VCardKey("ADR", List(("type","WORK"), ("type","pref"))),List("", "", "3494 Kuhl Avenue", "Atlanta", "GA", "30303", "USA")),
+ VCardEntry(VCardKey("X-ABADR",Nil),List("ca")),
+ VCardEntry(VCardKey("ADR", List(("type","HOME"))),List("", "", "1234 Laurel Street", "Atlanta", "GA", "30303", "USA")),
+ VCardEntry(VCardKey("X-ABADR",Nil),List("us")),
+ VCardEntry(VCardKey("BDAY",Nil),List("1980-06-22")),
+ VCardEntry(VCardKey("END", Nil), List("VCARD")))
+ }
+ }
+
+ }
+
+
+ }
+
+}
diff --git a/core/util/src/test/scala-3/net/liftweb/util/XmlParserSpec.scala b/core/util/src/test/scala-3/net/liftweb/util/XmlParserSpec.scala
new file mode 100644
index 000000000..8299bee26
--- /dev/null
+++ b/core/util/src/test/scala-3/net/liftweb/util/XmlParserSpec.scala
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2007-2011 WorldWide Conferencing, LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.liftweb
+package util
+
+import java.io.ByteArrayInputStream
+
+import scala.xml.{Text, Unparsed}
+
+import org.specs2.matcher.XmlMatchers
+import org.specs2.mutable.Specification
+
+
+/**
+ * Systems under specification for XmlParser, specifically PCDataMarkupParser.
+ */
+class XmlParserSpec extends Specification with XmlMatchers {
+ "Xml Parser Specification".title
+
+ "Multiple attributes with same name, but different namespace" should {
+ "parse correctly" >> {
+ val actual =
+
+
+
+
+ val expected =
+
+
+
+
+ val bis = new ByteArrayInputStream(actual.toString.getBytes("UTF-8"))
+ val parsed = PCDataXmlParser(bis).openOrThrowException("Test")
+ parsed must ==/(expected)
+ }
+
+ }
+
+ "XML can contain PCData" in {
+ val data = {
+ PCData("Hello Yak")
+ }
+
+ val str = AltXML.toXML(data, false, true)
+
+ str.indexOf("{
+ Unparsed("Hello & goodbye >
+
+ val str = AltXML.toXML(data, false, true)
+
+ str.indexOf("Hello & goodbye >
+ {
+ '\u0085'
+ }{
+ Text("hello \u0000 \u0085 \u0080")
+ }{
+ "hello \u0000 \u0003 \u0085 \u0080"
+ }{
+ '\u0003'
+ }
+
+
+ val str = AltXML.toXML(data, false, true)
+
+ def cntIllegal(in: Char): Int = in match {
+ case '\u0085' => 1
+ case c if (c >= '\u007f' && c <= '\u0095') => 1
+ case '\n' => 0
+ case '\r' => 0
+ case '\t' => 0
+ case c if c < ' ' => 1
+ case _ => 0
+ }
+
+ str.toList.foldLeft(0)((a, b) => a + cntIllegal(b)) === 0
+ }
+
+}
+
diff --git a/web/testkit/src/test/scala/net/liftweb/http/testing/MockHttpRequestSpec.scala b/web/testkit/src/test/scala-2.13/net/liftweb/http/testing/MockHttpRequestSpec.scala
similarity index 100%
rename from web/testkit/src/test/scala/net/liftweb/http/testing/MockHttpRequestSpec.scala
rename to web/testkit/src/test/scala-2.13/net/liftweb/http/testing/MockHttpRequestSpec.scala
diff --git a/web/testkit/src/test/scala-3/net/liftweb/http/testing/MockHttpRequestSpec.scala b/web/testkit/src/test/scala-3/net/liftweb/http/testing/MockHttpRequestSpec.scala
new file mode 100644
index 000000000..ed4da5cb8
--- /dev/null
+++ b/web/testkit/src/test/scala-3/net/liftweb/http/testing/MockHttpRequestSpec.scala
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2011 WorldWide Conferencing, LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.liftweb
+package mocks
+
+import org.specs2.mutable.Specification
+
+import org.json4s.JsonDSL._
+
+/**
+ * System under specification for MockHttpRequest.
+ */
+class MockHttpRequestSpec extends Specification {
+ "MockHttpRequest Specification".title
+
+ val IF_MODIFIED_HEADER = "If-Modified-Since"
+ val TEST_URL = "https://foo.com/test/this/page?a=b&b=a&a=c"
+ val TEST_URL_BLANK_PARAMETER = "https://foo.com/test/this/page?a=b&b=a&c=&d"
+ val TEST_URL_BLANK_PARAMETER_SERIALIZED = "https://foo.com/test/this/page?a=b&b=a&c=&d="
+
+ "MockHttpRequest" should {
+
+ "properly deconstruct from a URL" in {
+ val testRequest = new MockHttpServletRequest(TEST_URL, "/test")
+
+ testRequest.getScheme === "https"
+ testRequest.isSecure === true
+ testRequest.getServerName === "foo.com"
+ testRequest.getContextPath === "/test"
+ testRequest.getRequestURI === "/test/this/page"
+ testRequest.getRequestURL.toString === TEST_URL
+ testRequest.getQueryString === "a=b&b=a&a=c"
+ testRequest.getParameterValues("a").toList === List("b","c")
+ testRequest.getParameter("b") === "a"
+ }
+
+ "parse parameters with empty values" in {
+ val testRequest = new MockHttpServletRequest(TEST_URL_BLANK_PARAMETER, "/test")
+
+ testRequest.getScheme === "https"
+ testRequest.isSecure === true
+ testRequest.getServerName === "foo.com"
+ testRequest.getContextPath === "/test"
+ testRequest.getRequestURI === "/test/this/page"
+ testRequest.getRequestURL.toString === TEST_URL_BLANK_PARAMETER_SERIALIZED
+ testRequest.getQueryString === "a=b&b=a&c=&d="
+ testRequest.getParameter("c") === ""
+ testRequest.getParameter("d") === ""
+ }
+
+ "correctly add and parse a date header" in {
+ val testRequest = new MockHttpServletRequest(TEST_URL, "/test")
+
+ val epoch = 241000 // (milliseconds not included in RFC 1123)
+
+ testRequest.setDateHeader(IF_MODIFIED_HEADER, epoch)
+
+ testRequest.getDateHeader(IF_MODIFIED_HEADER) === epoch
+ }
+
+ "throw an IllegalArgumentException for an invalid date header" in {
+ val testRequest = new MockHttpServletRequest(TEST_URL, "/test")
+
+ testRequest.headers += IF_MODIFIED_HEADER -> List("this is not a valid date")
+
+ testRequest.getDateHeader(IF_MODIFIED_HEADER) must throwA[IllegalArgumentException]
+ }
+
+ "throw an IllegalArgumentException for an invalid context path" in {
+ (new MockHttpServletRequest(TEST_URL, "foo")) must throwA[IllegalArgumentException]
+ (new MockHttpServletRequest(TEST_URL, "/foo/")) must throwA[IllegalArgumentException]
+ }
+
+ "throw an IllegalArgumentException for an invalid query string" in {
+ val testRequest = new MockHttpServletRequest(TEST_URL, "/test")
+
+ (testRequest.queryString ="this=a&&that=b") must throwA[IllegalArgumentException]
+ }
+
+
+ "properly set a default content type for JSON" in {
+ val testRequest = new MockHttpServletRequest(TEST_URL, "/test")
+
+ testRequest.body_=("name" -> "joe")
+
+ testRequest.contentType === "application/json"
+ }
+
+ "properly set a user-specificed content type for JSON" in {
+ val testRequest = new MockHttpServletRequest(TEST_URL, "/test")
+
+ testRequest.body_=(("name" -> "joe"), "text/json")
+
+ testRequest.contentType === "text/json"
+ }
+
+ "properly set a default content type for XML" in {
+ val testRequest = new MockHttpServletRequest(TEST_URL, "/test")
+
+ testRequest.body_=()
+
+ testRequest.contentType === "text/xml"
+ }
+
+ "properly set a user-specificed content type for XML" in {
+ val testRequest = new MockHttpServletRequest(TEST_URL, "/test")
+
+ testRequest.body_=(, "application/xml")
+
+ testRequest.contentType === "application/xml"
+ }
+
+ "properly set a default content type for a String" in {
+ val testRequest = new MockHttpServletRequest(TEST_URL, "/test")
+
+ testRequest.body_=("test")
+
+ testRequest.contentType === "text/plain"
+ }
+
+ "properly set a user-specificed content type for a String" in {
+ val testRequest = new MockHttpServletRequest(TEST_URL, "/test")
+
+ testRequest.body_=("test", "text/csv")
+
+ testRequest.contentType === "text/csv"
+ }
+
+ }
+}