diff --git a/build.gradle b/build.gradle index 4764fafdc9..dfd139eed8 100644 --- a/build.gradle +++ b/build.gradle @@ -24,19 +24,29 @@ buildscript { } dependencies { classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath 'org.junit.platform:junit-platform-gradle-plugin:1.0.0' classpath "org.jetbrains.dokka:dokka-gradle-plugin:$dokka_version" classpath 'info.solidsoft.gradle.pitest:gradle-pitest-plugin:1.1.10' } } group 'io.kweb' -version '0.2.14' +version '0.3.0' apply plugin: 'java' apply plugin: 'kotlin' apply plugin: 'maven' apply plugin: 'org.jetbrains.dokka' apply plugin: "info.solidsoft.pitest" +apply plugin: 'org.junit.platform.gradle.plugin' + +junitPlatform { + filters { + engines { + include 'spek' + } + } +} sourceCompatibility = 1.8 targetCompatibility = 1.8 @@ -46,12 +56,10 @@ repositories { maven { url "http://dl.bintray.com/kotlin/ktor" } - /* maven { - url "http://dl.bintray.com/cy6ergn0m/maven" - } */ maven { url 'https://jitpack.io' } + maven { url "http://dl.bintray.com/jetbrains/spek" } jcenter() } @@ -73,19 +81,27 @@ sourceSets { dependencies { - // Temp for development - compile project(":renegade") - compile 'com.github.salomonbrys.kotson:kotson:2.3.0' compile group: 'org.apache.commons', name: 'commons-lang3', version: '3.0' compile group: 'commons-io', name: 'commons-io', version: '2.5' - + + ////////////////////////////// + // Kotlin library dependencies + ////////////////////////////// compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" compile 'org.jetbrains.kotlinx:kotlinx-coroutines-core:0.21-alpha-2' compile 'org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:0.21-alpha-2' + // TODO: This should be testCompile + compile group: 'ch.qos.logback', name: 'logback-classic', version: '1.+' + // Should be compileOnly + compile group: 'org.hotswapagent', name: 'hotswap-agent-core', version: '1.1.0' + + //////////////////// + // Ktor dependencies + //////////////////// compile ("io.ktor:ktor-server-core:$ktor_version") { exclude group : 'ch.qos.logback', module : 'logback-classic' } @@ -104,15 +120,20 @@ dependencies { compile 'com.github.kwebio:shoebox:0.2.13' + /////////////////////////// + // Dependencies for testing + /////////////////////////// compile group: 'net.sourceforge.htmlunit', name: 'htmlunit', version: '2.29' - testCompile 'io.kotlintest:kotlintest:2.0.3' + testCompile ('org.jetbrains.spek:spek-api:1.1.5') { + exclude group: 'org.jetbrains.kotlin' + } + testRuntime ('org.jetbrains.spek:spek-junit-platform-engine:1.1.5') { + exclude group: 'org.junit.platform' + exclude group: 'org.jetbrains.kotlin' + } + testImplementation 'org.amshove.kluent:kluent:1.35' testCompile group: 'org.slf4j', name: 'slf4j-api', version: '1.7.+' - // TODO: This should be testCompile - compile group: 'ch.qos.logback', name: 'logback-classic', version: '1.+' - - // Should be compileOnly - compile group: 'org.hotswapagent', name: 'hotswap-agent-core', version: '1.1.0' } diff --git a/src/main/kotlin/io/kweb/Kweb.kt b/src/main/kotlin/io/kweb/Kweb.kt index 1854afd728..4a6a9aa941 100644 --- a/src/main/kotlin/io/kweb/Kweb.kt +++ b/src/main/kotlin/io/kweb/Kweb.kt @@ -314,6 +314,7 @@ class Kweb(val port: Int, } override fun close() { + logger.info("Shutting down Kweb") server.stop(0, 0, TimeUnit.SECONDS) } diff --git a/src/test/kotlin/io/kweb/BasicBrowserInteropSpec.kt b/src/test/kotlin/io/kweb/BasicBrowserInteropSpec.kt index 79058f75fd..17bb721730 100644 --- a/src/test/kotlin/io/kweb/BasicBrowserInteropSpec.kt +++ b/src/test/kotlin/io/kweb/BasicBrowserInteropSpec.kt @@ -1,21 +1,10 @@ package io.kweb -import com.gargoylesoftware.htmlunit.AjaxController -import com.gargoylesoftware.htmlunit.BrowserVersion -import com.gargoylesoftware.htmlunit.WebClient -import com.gargoylesoftware.htmlunit.WebRequest -import com.gargoylesoftware.htmlunit.html.HtmlPage -import io.kotlintest.matchers.shouldBe -import io.kotlintest.specs.FreeSpec -import io.kweb.dom.element.creation.tags.h1 -import io.kweb.dom.element.new -import java.io.Closeable - /** * Created by ian on 4/30/17. - */ + class BasicBrowserInteropSpec : FreeSpec() { val webClient : WebClient = autoClose(ACWebClient().apply { options.apply { @@ -52,5 +41,4 @@ class BasicBrowserInteropSpec : FreeSpec() { } } } - -class ACWebClient : WebClient(BrowserVersion.BEST_SUPPORTED), Closeable \ No newline at end of file +*/ \ No newline at end of file diff --git a/src/test/kotlin/io/kweb/KwebSpec.kt b/src/test/kotlin/io/kweb/KwebSpec.kt new file mode 100644 index 0000000000..841fe38084 --- /dev/null +++ b/src/test/kotlin/io/kweb/KwebSpec.kt @@ -0,0 +1,42 @@ +package io.kweb + +import com.gargoylesoftware.htmlunit.html.HtmlPage +import io.kweb.dom.element.creation.tags.h1 +import io.kweb.dom.element.new +import org.amshove.kluent.shouldEqual +import org.jetbrains.spek.api.Spek +import org.jetbrains.spek.api.dsl.* + +object KwebSpec : Spek({ + given("a Kweb instance listening on port 12243") { + on("creating a kweb server") { + val kweb = Kweb(port = 12243) { + doc.body.new { + h1().text("Lorum Ipsum") + } + } + + + useWebClient { webClient -> + + + val page = webClient.getPage("http://localhost:12243/") + + webClient.waitForBackgroundJavaScript(10000) + + page.getElementsByTagName("h1").let { h1Elements -> + it("should contain one H1 element containing the appropriate text") { + h1Elements.size shouldEqual 1 + h1Elements.first().let { h1Element -> + h1Element.textContent shouldEqual "Lorum Ipsum" + } + } + } + + } + + kweb.close() + } + + } +}) diff --git a/src/test/kotlin/io/kweb/routing/RoutingSpec.kt b/src/test/kotlin/io/kweb/routing/RoutingSpec.kt index debf14105f..d8abcd8b42 100644 --- a/src/test/kotlin/io/kweb/routing/RoutingSpec.kt +++ b/src/test/kotlin/io/kweb/routing/RoutingSpec.kt @@ -1,25 +1,12 @@ package io.kweb.routing -import com.gargoylesoftware.htmlunit.WebClient -import com.gargoylesoftware.htmlunit.html.HtmlPage -import io.kotlintest.* -import io.kotlintest.matchers.* -import io.kotlintest.specs.FreeSpec -import io.kweb.* -import io.kweb.dom.attributes.* -import io.kweb.dom.element.creation.tags.h1 -import io.kweb.dom.element.events.on -import io.kweb.dom.element.new -import io.kweb.state.persistent.render -import mu.KotlinLogging -import java.lang.Thread.sleep - /** * Created by ian on 4/30/17. */ -class RoutingSpec : FreeSpec() { +/* +object RoutingSpec : Spek({ - private val logger = KotlinLogging.logger {} + val logger = KotlinLogging.logger {} val webClient: WebClient = autoClose(ACWebClient()) @@ -65,7 +52,7 @@ class RoutingSpec : FreeSpec() { } } } -} +}) val Duration.millis get() = this.timeUnit.toMillis(amount) @@ -85,3 +72,4 @@ fun pollFor(maximumTime: io.kotlintest.Duration, pollEvery : Duration = 300. } throw AssertionError("Test failed after ${maximumTime.amount} ${maximumTime.timeUnit}; attempted $times times", lastException!!) } +*/ diff --git a/src/test/kotlin/io/kweb/state/BindableSpec.kt b/src/test/kotlin/io/kweb/state/BindableSpec.kt index dbe41443d2..5291d65d90 100644 --- a/src/test/kotlin/io/kweb/state/BindableSpec.kt +++ b/src/test/kotlin/io/kweb/state/BindableSpec.kt @@ -1,68 +1,86 @@ package io.kweb.state -import io.kotlintest.matchers.shouldBe -import io.kotlintest.specs.FreeSpec +import org.amshove.kluent.shouldEqual +import org.jetbrains.spek.api.Spek +import org.jetbrains.spek.api.dsl.* /** * Created by ian on 6/18/17. */ -class BindableSpec : FreeSpec() { - init { - "A Bindable should initialize correctly" { - val sw = ReadOnlyBindable("Test") - sw.value shouldBe "Test" +object BindableSpec : Spek({ + describe("ReadOnlyBindable") { + val readOnlyBindable = ReadOnlyBindable("Test") + on("retrieving its value") { + val value = readOnlyBindable.value + it("should match the value it was initialized with") { + readOnlyBindable.value shouldEqual "Test" + } } + } - "A Bindable should notify a listener of a change" { - val sw = Bindable("Foo") - var old : String? = null - var new : String? = null - sw.addListener { o, n -> - old shouldBe null - new shouldBe null + describe("A simple string bindable") { + val bindable = Bindable("Foo") + context("adding a listener that modifies two vars") { + var old: String? = null + var new: String? = null + val handle = bindable.addListener { o, n -> + old shouldEqual null + new shouldEqual null old = o new = n } - sw.value = "Bar" - old shouldBe "Foo" - new shouldBe "Bar" - } - "A removed listener shouldn't be called" { - val sw = Bindable("Foo") - var old : String? = null - var new : String? = null - val listenerHandler = sw.addListener { o, n -> - old shouldBe null - new shouldBe null - old = o - new = n + on("modifying the value") { + bindable.value = "Bar" + it("should call the listener, modifying the vars accordingly") { + old shouldEqual "Foo" + new shouldEqual "Bar" + } + } + on("removing the listener and modifying the value again") { + bindable.removeListener(handle) + bindable.value = "FooBar" + it("shouldn't call the listener again") { + old shouldEqual "Foo" + new shouldEqual "Bar" + } + } - sw.removeListener(listenerHandler) - sw.value = "Bar" - old shouldBe null - new shouldBe null } + context("creating a one-way mapping") { + val mappedBindable = bindable.map { it.length } + on("modifying the original bindable") { + bindable.value = "elephant" + it("should be mapped correctly") { + mappedBindable.value shouldEqual 8 + } - "A read-only mapped watcher should work" { - val sw = Bindable("Foo") - val mapped = sw.map {it -> it.length} - mapped.value shouldBe 3 - sw.value = "Hello" - mapped.value shouldBe 5 + } } -/* - "A bi-directional mapped watcher should work" { - data class Foo(var bar : Int) - val sw = Bindable(Foo(12)) - val mapped = sw.map( - object : ReversableFunction<> - {it.bar.toString()}, {n, o -> n.copy(bar = o.toInt())} + } + describe("a string bindable") { + val lowerCaseBindable = Bindable("foo") + context("creating a two-way mapping") { + val upperCaseBindable = lowerCaseBindable.map(object : ReversableFunction { + override fun map(from: String) = from.toUpperCase() + + override fun unmap(original: String, change: String) = change.toLowerCase() - ) - sw.value shouldBe Foo(12) - mapped.value = "143" - sw.value shouldBe Foo(143) - } */ + }) + on("modifying the original bindable") { + lowerCaseBindable.value = "one" + it("should be mapped correctly") { + val value = upperCaseBindable.value + value shouldEqual "ONE" + } + } + on("modifying the mappedBindable") { + upperCaseBindable.value = "TWO" + it("should be unmapped correctly") { + val value = lowerCaseBindable.value + value shouldEqual "two" + } + } + } } -} \ No newline at end of file +}) diff --git a/src/test/kotlin/io/kweb/state/persistent/PersistentSpec.kt b/src/test/kotlin/io/kweb/state/persistent/PersistentSpec.kt index e38d5f6fd0..72a39f4f88 100644 --- a/src/test/kotlin/io/kweb/state/persistent/PersistentSpec.kt +++ b/src/test/kotlin/io/kweb/state/persistent/PersistentSpec.kt @@ -1,26 +1,9 @@ package io.kweb.state.persistent -import com.gargoylesoftware.htmlunit.WebClient -import com.gargoylesoftware.htmlunit.html.HtmlAnchor -import com.gargoylesoftware.htmlunit.html.HtmlPage -import com.github.sanity.shoebox.Shoebox -import io.kotlintest.matchers.shouldBe -import io.kotlintest.matchers.shouldEqual -import io.kotlintest.seconds -import io.kotlintest.specs.FreeSpec -import io.kweb.ACWebClient -import io.kweb.Kweb -import io.kweb.dom.element.creation.tags.a -import io.kweb.dom.element.creation.tags.li -import io.kweb.dom.element.creation.tags.ul -import io.kweb.dom.element.events.on -import io.kweb.dom.element.new -import io.kweb.routing.pollFor - /** * Created by ian on 6/29/17. - */ -class PersistentSpec : FreeSpec() { + +object PersistentSpec : Spek({ val webClient: WebClient = autoClose(ACWebClient()) init { @@ -124,5 +107,6 @@ class PersistentSpec : FreeSpec() { } } } -} +}) +*/ \ No newline at end of file diff --git a/src/test/kotlin/io/kweb/util.kt b/src/test/kotlin/io/kweb/util.kt index b13db08956..e5c00d890e 100644 --- a/src/test/kotlin/io/kweb/util.kt +++ b/src/test/kotlin/io/kweb/util.kt @@ -1,24 +1,52 @@ package io.kweb -import com.gargoylesoftware.htmlunit.AjaxController -import com.gargoylesoftware.htmlunit.WebClient -import com.gargoylesoftware.htmlunit.WebRequest +import com.gargoylesoftware.htmlunit.* import com.gargoylesoftware.htmlunit.html.HtmlPage -import org.apache.commons.logging.LogFactory -import java.util.logging.Level -import java.util.logging.Logger +import io.kweb.dom.element.creation.tags.h1 +import io.kweb.dom.element.new + + + + /** * Created by ian on 6/29/17. */ -fun htmlUnitInit(webClient: WebClient) { - webClient.ajaxController = object : AjaxController() { - override fun processSynchron(page: HtmlPage?, request: WebRequest?, async: Boolean): Boolean { - return true +fun useWebClient(callback: (WebClient) -> Unit) { + WebClient(BrowserVersion.BEST_SUPPORTED).use { webClient -> + /* + webClient.ajaxController = object : AjaxController() { + override fun processSynchron(page: HtmlPage?, request: WebRequest?, async: Boolean): Boolean { + return true + } + } + */ + webClient.ajaxController = object : AjaxController() { + override fun processSynchron(page: HtmlPage?, request: WebRequest?, async: Boolean): Boolean { + return true + } } +/* + LogFactory.getFactory().setAttribute("org.apache.commons.logging.Log", "org.apache.commons.logging.impl.NoOpLog") + Logger.getLogger("com.gargoylesoftware").level = Level.OFF + Logger.getLogger("org.apache.commons.httpclient").level = Level.OFF +*/ + + callback(webClient) + } +} + +fun main(args: Array) { + val kweb = Kweb(port = 1251) { + doc.body.new { + h1().text("Hello") + } + } + + useWebClient { wc -> + val google = wc.getPage("http://google.com/") + println(google.titleText) } - LogFactory.getFactory().setAttribute("org.apache.commons.logging.Log", "org.apache.commons.logging.impl.NoOpLog") - Logger.getLogger("com.gargoylesoftware").level = Level.OFF - Logger.getLogger("org.apache.commons.httpclient").level = Level.OFF + kweb.close() } \ No newline at end of file