Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
package com.github.mvysny.kaributesting.v8
import com.github.mvysny.fakeservlet.*
import com.vaadin.server.*
import com.vaadin.shared.Version
import com.vaadin.shared.ui.ui.PageClientRpc
import com.vaadin.ui.UI
import com.vaadin.util.CurrentInstance
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import java.util.concurrent.ExecutionException
import java.util.concurrent.locks.ReentrantLock
import javax.servlet.ServletContext
private class MockVaadinService(servlet: VaadinServlet, deploymentConfiguration: DeploymentConfiguration) : VaadinServletService(servlet, deploymentConfiguration) {
override fun isAtmosphereAvailable() = false
}
private class MockVaadinServlet : VaadinServlet() {
override fun createServletService(deploymentConfiguration: DeploymentConfiguration): VaadinServletService {
val service = MockVaadinService(this, deploymentConfiguration)
service.init()
return service
}
public override fun getService(): VaadinServletService =
super.getService()
}
private class MockVaadinSession(val httpSession: FakeHttpSession,
service: VaadinService,
private val uiFactory: () -> UI) : VaadinSession(service) {
override fun close() {
super.close()
// We need to simulate the actual browser + servlet container behavior here.
// Imagine that we want a test scenario where the user logs out, and we want to check that a login prompt appears.
// To log out the user, the code typically closes the session and tells the browser to reload
// the page (Page.getCurrent().reload() or similar).
// Thus the page is reloaded by the browser, and since the session is gone, the servlet container
// will create a new, fresh session.
// That's exactly what we need to do here. We need to close the current UI and eradicate it,
// then we need to close the current session and eradicate it, and then we need to create a completely fresh
// new UI and Session.
// A problem appears when the uiFactory accidentally doesn't create a new, fresh instance of UI. Say that
// we call Spring injector to provide us an instance of the UI, but we accidentally scoped the UI to Session.
// Spring doesn't know that (since we haven't told Spring that the Session scope is gone) and provides
// the previous UI instance which is still attached to the session. And it blows.
MockVaadin.clearVaadinObjects()
httpSession.destroy()
MockVaadin.createSession(httpSession, uiFactory)
}
}
public object MockVaadin {
// prevent GC on Vaadin Session and Vaadin UI as they are only soft-referenced from the Vaadin itself.
private var strongRefSession: VaadinSession? = null
private var strongRefUI: UI? = null
private var strongRefRequest: VaadinRequest? = null
private var strongRefResponse: VaadinResponse? = null
private var lastLocation: String? = null
internal val log: Logger = LoggerFactory.getLogger(MockVaadin::class.java)
/**
* Creates new mock session and UI for a test. Just call this before all and every of your UI tests are ran.
*
* The UI factory *must* provide a new, fresh instance of the UI, so that the
* tests start from a pre-known state. If you're using Spring and you're getting UI
* from the injector, you must reconfigure Spring to use prototype scope,
* otherwise an old UI from the UI scope or Session Scope will be provided.
* @param uiFactory called once from this method, to provide instance of your app's UI. By default it returns [MockUI].
* A basic Vaadin environment is prepared before calling this factory, in order to be safe to instantiate your UI.
* To instantiate your UI just call your UI constructor, for example `YourUI()`.
* @param httpSession allow you to use custom http session. Useful for MPR where Vaadin 8 + Vaadin 10 share one session
* @param servletContext the servlet context to use
*/
@JvmStatic
@JvmOverloads
public fun setup(uiFactory: () -> UI = @JvmSerializableLambda { MockUI() },
httpSession: FakeHttpSession? = null,
servletContext: ServletContext = FakeContext()) {
val enableProductionModeTemporarily: Boolean = System.getProperty("vaadin.productionMode") == null
if (enableProductionModeTemporarily) {
// set the production mode to true, to suppress the repeated annoying warning message:
// WARNING:
// =================================================================
// Vaadin is running in DEBUG MODE.
// Add productionMode=true to web.xml to disable debug features.
// To show debug window, add ?debug to your application URL.
// =================================================================
// The warning is logged by DefaultDeploymentConfiguration.checkProductionMode():269
// However, this setting affects Vaadin 10+ as well, so we need to reset it after
// we suppressed the annoying message
System.setProperty("vaadin.productionMode", "true")
}
// prepare mocking servlet environment
val servlet = MockVaadinServlet()
try {
servlet.init(FakeServletConfig(servletContext))
} finally {
if (enableProductionModeTemporarily) {
System.clearProperty("vaadin.productionMode")
}
}
val httpSess: FakeHttpSession = httpSession
?: FakeHttpSession.create(servletContext)
// mock Vaadin environment: Service
VaadinService.setCurrent(servlet.service)
// Session
createSession(httpSess, uiFactory)
}
/**
* Cleans up and removes the Vaadin UI and Vaadin Session. You can call this function in `afterEach{}` block,
* to clean up after the test. This comes handy when you want to be extra-sure that the next test won't accidentally reuse old UI,
* should you forget to call [setup] properly.
*
* You don't have to call this function though; [setup] will overwrite any current UI/Session instances with a fresh ones.
*/
@JvmStatic
public fun tearDown() {
clearVaadinObjects()
VaadinService.setCurrent(null)
lastLocation = null
}
internal fun clearVaadinObjects() {
closeCurrentUI()
closeCurrentSession()
CurrentInstance.set(VaadinRequest::class.java, null)
CurrentInstance.set(VaadinResponse::class.java, null)
strongRefRequest = null
strongRefResponse = null
}
private fun closeCurrentSession() {
VaadinSession.setCurrent(null)
strongRefSession = null
}
internal fun createSession(httpSession: FakeHttpSession, uiFactory: () -> UI) {
val service: VaadinServletService = checkNotNull(VaadinService.getCurrent()) as VaadinServletService
val session = MockVaadinSession(httpSession, service, uiFactory)
httpSession.setAttribute(service.serviceName + ".lock", ReentrantLock().apply { lock() })
session.refreshTransients(WrappedHttpSession(httpSession), service)
session.configuration = checkNotNull(service.deploymentConfiguration)
VaadinSession.setCurrent(session)
strongRefSession = session
// request
val httpRequest = FakeRequest(httpSession)
httpRequest.setParameter("v-loc", "http://localhost:8080")
strongRefRequest = VaadinServletRequest(httpRequest, service)
CurrentInstance.set(VaadinRequest::class.java, strongRefRequest)
// response
strongRefResponse = VaadinServletResponse(FakeResponse(), service)
CurrentInstance.set(VaadinResponse::class.java, strongRefResponse)
// UI
createUI(uiFactory)
}
private fun closeCurrentUI() {
val ui: UI = UI.getCurrent() ?: return
lastLocation = try {
Page.getCurrent().location.path.trim('/')
} catch (t: Exception) {
// incorrectly mocked Page tends to fail in Page.getLocation(). Just do nothing.
log.warn("Failed to retrieve current location from Page, probably because of incorrectly mocked Vaadin classes", t)
null
}
ui.close()
if (ui.isAttached) {
// incorrectly mocked UI can fail in mysterious ways in detach()
ui.detach()
}
UI.setCurrent(null)
strongRefUI = null
}
private fun createUI(uiFactory: () -> UI) {
val ui = uiFactory()
require(ui.session == null) {
"uiFactory produced UI $ui which is already attached to a Session, " +
"yet we expect the UI to be a fresh new instance, not yet attached to a Session, so that the" +
" tests are able to always start with a fresh UI with a pre-known state. Perhaps you're " +
"using Spring which reuses a scoped instance of the UI?"
}
ui.session = checkNotNull(VaadinSession.getCurrent())
val request = checkNotNull(CurrentInstance.get(VaadinRequest::class.java))
strongRefUI = ui
UI.setCurrent(ui)
ui.page.webBrowser.updateRequestDetails(request)
if (Version.getMinorVersion() >= 2) {
// uiRootPath field is only present for Vaadin 8.2.x and higher.
UI::class.java.getDeclaredField("uiRootPath").apply {
isAccessible = true
set(ui, "")
}
}
try {
ui.doInit(request, 1, "1")
} catch (e: Exception) {
if (ui.navigator != null) {
throw RuntimeException("UI failed to initialize. If you're using autoViewProvider, make sure that views are auto-discovered via autoDiscoverViews()", e)
} else {
throw e
}
}
// catch Page.getCurrent().reload() requests
ui.overrideRpcProxy(PageClientRpc::class.java, object : PageClientRpc {
override fun reload() {
closeCurrentUI()
createUI(uiFactory)
}
override fun initializeMobileHtml5DndPolyfill() {
}
})
if (!lastLocation.isNullOrBlank()) {
UI.getCurrent().navigator.navigateTo(lastLocation)
lastLocation = null
}
}
/**
* Since Karibu-Testing runs in the same JVM as the server and there is no browser, the boundaries between the client and
* the server become unclear. When looking into sources of any test method, it's really hard to tell where exactly the server request ends, and
* where another request starts.
*
* You can establish an explicit client boundary in your test, by explicitly calling this method. However, since that
* would be both laborous and error-prone, the default operation is that Karibu Testing pretends as if there was a client-server
* roundtrip before every component lookup
* via the [_get]/[_find]/[_expectNone]/[_expectOne] call. See [TestingLifecycleHook] for more details.
*
* Calls [runUIQueue].
* @throws IllegalStateException if the environment is not mocked
*/
public fun clientRoundtrip() {
runUIQueue()
}
/**
* Runs all tasks scheduled by [UI.access].
*
* If [VaadinSession.errorHandler] is not set or [propagateExceptionToHandler]
* is false, any exceptions thrown from [Runnable]s scheduled via the [UI.access] will make this function fail.
* The exceptions will be wrapped in [ExecutionException]. Generally
* it's best to keep [propagateExceptionToHandler] set to false to
* make any exceptions fail the test; however if you're testing
* how your own custom [VaadinSession.errorHandler] responds to exceptions then
* set this parameter to true.
*
* Called automatically by [clientRoundtrip] which is by default called automatically from [TestingLifecycleHook]. You generally
* don't need to call this method unless you need to test your [ErrorHandler].
*
* @param propagateExceptionToHandler defaults to false. If true and [VaadinSession.errorHandler]
* is set, any exceptions thrown from [Runnable]s scheduled via the [UI.access] will be
* redirected to [VaadinSession.errorHandler] and will not be re-thrown from this method.
* @throws IllegalStateException if the environment is not mocked
*/
public fun runUIQueue(propagateExceptionToHandler: Boolean = false) {
checkNotNull(VaadinSession.getCurrent()) { "No VaadinSession" }
VaadinSession.getCurrent()!!.apply {
// we need to set up UI error handler which will be notified for every exception thrown out of the acccess{} block
// otherwise the exceptions would simply be logged but unlock() wouldn't fail.
val errors = mutableListOf()
val oldErrorHandler = UI.getCurrent().errorHandler
if (oldErrorHandler == null || oldErrorHandler is DefaultErrorHandler || !propagateExceptionToHandler) {
UI.getCurrent().errorHandler = ErrorHandler {
var t = it.throwable
if (t !is ExecutionException) {
// for some weird reason t may not be ExecutionException when it originates from a coroutine :confused:
// the stacktrace would point someplace random. Wrap it in ExecutionException whose stacktrace will point to the test
t = ExecutionException(t.message, t)
}
errors.add(t)
}
}
try {
unlock() // this will process all Runnables registered via ui.access()
// lock the session back, so that the test can continue running as-if in the UI thread.
lock()
} finally {
UI.getCurrent().errorHandler = oldErrorHandler
}
if (!errors.isEmpty()) {
errors.drop(1).forEach { errors[0].addSuppressed(it) }
throw errors[0]
}
}
}
}
/**
* An empty mock UI.
*/
internal class MockUI : UI() {
override fun init(request: VaadinRequest?) {
}
}
public val currentRequest: VaadinRequest
get() = VaadinService.getCurrentRequest()
?: throw IllegalStateException("No current request. Have you called MockVaadin.setup()?")
public val currentResponse: VaadinResponse
get() = VaadinService.getCurrentResponse()
?: throw IllegalStateException("No current response. Have you called MockVaadin.setup()?")
/**
* Returns the [UI.getCurrent]; fails with informative error message if the UI.getCurrent() is null.
*/
public val currentUI: UI
get() = UI.getCurrent()
?: throw IllegalStateException("UI.getCurrent() is null. Have you called MockVaadin.setup()?")
/**
* Retrieves the mock request which backs up [VaadinRequest].
* ```
* currentRequest.mock.addCookie(Cookie("foo", "bar"))
* ```
*/
public val VaadinRequest.mock: FakeRequest get() = (this as VaadinServletRequest).request as FakeRequest
/**
* Retrieves the mock request which backs up [VaadinResponse].
* ```
* currentResponse.mock.getCookie("foo").value
* ```
*/
public val VaadinResponse.mock: FakeResponse get() = (this as VaadinServletResponse).response as FakeResponse
/**
* Retrieves the mock session which backs up [VaadinSession].
* ```
* VaadinSession.getCurrent().mock
* ```
*/
public val VaadinSession.mock: FakeHttpSession get() = (session as WrappedHttpSession).httpSession as FakeHttpSession