com.github.mvysny.kaributesting.v8.BasicUtils.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of karibu-testing-v8 Show documentation
Show all versions of karibu-testing-v8 Show documentation
Karibu Testing, support for browserless Vaadin testing in Kotlin
The newest version!
@file:Suppress("ObjectPropertyName", "FunctionName")
package com.github.mvysny.kaributesting.v8
import com.vaadin.annotations.Theme
import com.vaadin.data.HasValue
import com.vaadin.event.FieldEvents
import com.vaadin.server.AbstractClientConnector
import com.vaadin.server.VaadinRequest
import com.vaadin.shared.communication.ClientRpc
import com.vaadin.ui.*
import java.util.*
import kotlin.test.expect
import kotlin.test.fail
/**
* Allows us to fire any Vaadin event on any Vaadin component.
* @receiver the component, not null.
* @param event the event, not null.
*/
public fun AbstractClientConnector._fireEvent(event: EventObject) {
// fireEvent() is protected, gotta make it public
val fireEvent = AbstractClientConnector::class.java.getDeclaredMethod("fireEvent", EventObject::class.java)
fireEvent.isAccessible = true
fireEvent.invoke(this, event)
}
public val IntRange.size: Int get() = (endInclusive + 1 - start).coerceAtLeast(0)
/**
* Checks that a component is actually editable by the user:
* * The component must be effectively visible: it itself must be visible, its parent must be visible and all of its ascendants must be visible.
* For the purpose of testing individual components not attached to the [UI], a component may be considered visible even though it's not
* currently nested in a [UI].
* * The component must be effectively enabled: it itself must be enabled, its parent must be enabled and all of its ascendants must be enabled.
* * If the component is [HasValue], it must not be [HasValue.isReadOnly].
* @throws IllegalStateException if any of the above doesn't hold.
*/
public fun Component.checkEditableByUser() {
check(isEffectivelyVisible()) { "The ${toPrettyString()} is not effectively visible - either it is hidden, or its ascendant is hidden" }
check(isEnabled) { "The ${toPrettyString()} is not enabled" }
check(isEffectivelyEnabled()) { "The ${toPrettyString()} is nested in a disabled component" }
if (this is HasValue<*>) {
check(!this.isReadOnly) { "The ${toPrettyString()} is read-only" }
}
}
/**
* Fails if the component is editable. See [checkEditableByUser] for more details.
* @throws AssertionError if the component is editable.
*/
public fun Component.expectNotEditableByUser() {
try {
checkEditableByUser()
fail("The ${toPrettyString()} is editable")
} catch (ex: IllegalStateException) {
// okay
}
}
private fun Component.isEffectivelyEnabled(): Boolean = when {
!isEnabled -> false
// this is the difference to Component.isConnectorEnabled which in this case returns false.
// however, we should be perfectly able to test components not connected to a UI.
parent == null -> true
else -> parent.isEffectivelyEnabled()
}
/**
* Expects that [actual] list of objects matches [expected] list of objects. Fails otherwise.
*/
public fun expectList(vararg expected: T, actual: ()->List) {
expect(expected.toList(), actual)
}
/**
* Returns [Label.value] or [HasValue.getValue]; returns `null` if the receiver is neither of those two things.
*/
public val Component.value: Any? get()= when(this) {
is Label -> this.value
is HasValue<*> -> this.value
else -> null
}
/**
* Sets the value of given component, but only if it is actually possible to do so by the user, i.e. the component
* is enabled and is not read-only. If the component is read-only or disabled, an exception is thrown.
* @throws IllegalStateException if the field was not visible, not enabled or was read-only.
*/
public var HasValue._value: V?
get() = value
set(v) {
(this as Component).checkEditableByUser()
value = v
}
public fun AbstractClientConnector.overrideRpcProxy(rpcInterface: Class, instance: T) {
val rpcProxyMap: MutableMap, ClientRpc> = AbstractClientConnector::class.java.getDeclaredField("rpcProxyMap").run {
isAccessible = true
@Suppress("UNCHECKED_CAST")
get(this@overrideRpcProxy) as MutableMap, ClientRpc>
}
rpcProxyMap[rpcInterface] = instance
}
/**
* Returns [AbstractTextField.getPlaceholder]/[ComboBox.getPlaceholder]/[DateField.getPlaceholder]/[DateTimeField.getPlaceholder] or null
* for other components.
*/
public val Component.placeholder: String?
get() = when (this) {
is AbstractTextField -> placeholder
is ComboBox<*> -> this.placeholder // https://youtrack.jetbrains.com/issue/KT-24275
is DateField -> placeholder
is DateTimeField -> placeholder
else -> null
}
/**
* Checks whether this component matches given spec. All rules are matched except the [count] rule. The
* rules are matched against given component only (not against its children).
*/
public fun Component.matches(spec: SearchSpec.()->Unit): Boolean =
SearchSpec(Component::class.java).apply { spec() }.toPredicate().invoke(this)
/**
* Returns the current Vaadin theme (the theme that the current UI uses).
*/
public val currentTheme: String get() {
val ui = UI.getCurrent() ?: throw AssertionError("No current UI")
return ui.theme
?: ui.javaClass.getAnnotation(Theme::class.java)?.value
?: ui.session.service.getConfiguredTheme(VaadinRequest.getCurrent())
}
/**
* Fires [FieldEvents.FocusEvent] on the component, but only if it's editable.
*/
public fun T._focus() where T: Component.Focusable, T: FieldEvents.FocusNotifier {
checkEditableByUser()
focus()
(this as AbstractClientConnector)._fireEvent(FieldEvents.FocusEvent(this))
}
/**
* Fires [FieldEvents.BlurEvent] on the component, but only if it's editable.
*/
public fun T._blur() where T: Component, T: FieldEvents.BlurNotifier {
checkEditableByUser()
(this as AbstractClientConnector)._fireEvent(FieldEvents.BlurEvent(this))
}
public val UI._pendingFocus: Component? get() {
val pendingFocusField = UI::class.java.getDeclaredField("pendingFocus")
pendingFocusField.isAccessible = true
return pendingFocusField.get(this) as Component?
}