mirror of
https://github.com/jlengrand/compose-multiplatform.git
synced 2026-05-12 00:01:20 +00:00
Web/controlled inputs (#1031)
* web: make TextInput manage its view state * web: make TextInput manage its view state * web: make TextInput manage its view state * web: add tests for controlled TextArea and RadioButton * Add more tests for controlled and uncontrolled inputs * Add more tests for controlled and uncontrolled inputs * Update date input tests * Update time input tests * Update date input tests * Add docs for controlled and unctontrolled input modes * web: improve docs for controlled inputs * web: make benchmark add1kItems run only 2 times instead of 3 (to prevent it from throwing timeout) Co-authored-by: Oleksandr Karpovich <oleksandr.karpovich@jetbrains.com>
This commit is contained in:
committed by
GitHub
parent
03617c199d
commit
db53b95a31
@@ -65,8 +65,8 @@ class BenchmarkTests {
|
||||
return duration
|
||||
}
|
||||
|
||||
@Test // add1kItems overrides default `repeat` value (was - 5, now - 3) to avoid getting swallowed on CI
|
||||
fun add1kItems() = runBenchmark(name = "add1000Items", repeat = 3) {
|
||||
@Test // add1kItems overrides default `repeat` value (was - 5, now - 2) to avoid getting swallowed on CI
|
||||
fun add1kItems() = runBenchmark(name = "add1000Items", repeat = 2) {
|
||||
addNItems(1000)
|
||||
}
|
||||
|
||||
@@ -139,4 +139,4 @@ class BenchmarkTests {
|
||||
|
||||
duration
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,10 +6,7 @@ import org.jetbrains.compose.web.css.StyleHolder
|
||||
import org.jetbrains.compose.web.dom.setProperty
|
||||
import org.jetbrains.compose.web.dom.setVariable
|
||||
import kotlinx.dom.clear
|
||||
import org.w3c.dom.Element
|
||||
import org.w3c.dom.HTMLElement
|
||||
import org.w3c.dom.Node
|
||||
import org.w3c.dom.get
|
||||
import org.w3c.dom.*
|
||||
|
||||
internal class DomApplier(
|
||||
root: DomNodeWrapper
|
||||
@@ -109,7 +106,9 @@ internal class DomElementWrapper(override val node: HTMLElement): DomNodeWrapper
|
||||
fun updateProperties(list: List<Pair<(Element, Any) -> Unit, Any>>) {
|
||||
if (node.className.isNotEmpty()) node.className = ""
|
||||
|
||||
list.forEach { it.first(node, it.second) }
|
||||
list.forEach {
|
||||
it.first(node, it.second)
|
||||
}
|
||||
}
|
||||
|
||||
fun updateStyleDeclarations(style: StyleHolder?) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.jetbrains.compose.web.attributes
|
||||
|
||||
import org.jetbrains.compose.web.attributes.builders.saveControlledInputState
|
||||
import org.jetbrains.compose.web.events.SyntheticSubmitEvent
|
||||
import org.w3c.dom.HTMLAnchorElement
|
||||
import org.w3c.dom.HTMLButtonElement
|
||||
@@ -127,9 +128,6 @@ fun AttrsBuilder<HTMLInputElement>.autoFocus() =
|
||||
fun AttrsBuilder<HTMLInputElement>.capture(value: String) =
|
||||
attr("capture", value) // type: file only
|
||||
|
||||
fun AttrsBuilder<HTMLInputElement>.checked() =
|
||||
attr("checked", "") // radio, checkbox
|
||||
|
||||
fun AttrsBuilder<HTMLInputElement>.dirName(value: String) =
|
||||
attr("dirname", value) // text, search
|
||||
|
||||
@@ -202,14 +200,6 @@ fun AttrsBuilder<HTMLInputElement>.src(value: String) =
|
||||
fun AttrsBuilder<HTMLInputElement>.step(value: Number) =
|
||||
attr("step", value.toString()) // numeric types only
|
||||
|
||||
fun AttrsBuilder<HTMLInputElement>.valueAttr(value: String) =
|
||||
attr("value", value)
|
||||
|
||||
fun AttrsBuilder<HTMLInputElement>.value(value: String): AttrsBuilder<HTMLInputElement> {
|
||||
prop(setInputValue, value)
|
||||
return this
|
||||
}
|
||||
|
||||
/* Option attributes */
|
||||
|
||||
fun AttrsBuilder<HTMLOptionElement>.value(value: String) =
|
||||
@@ -299,11 +289,6 @@ fun AttrsBuilder<HTMLTextAreaElement>.rows(value: Int) =
|
||||
fun AttrsBuilder<HTMLTextAreaElement>.wrap(value: TextAreaWrap) =
|
||||
attr("wrap", value.str)
|
||||
|
||||
fun AttrsBuilder<HTMLTextAreaElement>.value(value: String): AttrsBuilder<HTMLTextAreaElement> {
|
||||
prop(setInputValue, value)
|
||||
return this
|
||||
}
|
||||
|
||||
/* Img attributes */
|
||||
|
||||
fun AttrsBuilder<HTMLImageElement>.src(value: String): AttrsBuilder<HTMLImageElement> =
|
||||
@@ -312,8 +297,19 @@ fun AttrsBuilder<HTMLImageElement>.src(value: String): AttrsBuilder<HTMLImageEle
|
||||
fun AttrsBuilder<HTMLImageElement>.alt(value: String): AttrsBuilder<HTMLImageElement> =
|
||||
attr("alt", value)
|
||||
|
||||
private val setInputValue: (HTMLInputElement, String) -> Unit = { e, v ->
|
||||
|
||||
internal val setInputValue: (HTMLInputElement, String) -> Unit = { e, v ->
|
||||
e.value = v
|
||||
saveControlledInputState(e, v)
|
||||
}
|
||||
|
||||
internal val setTextAreaDefaultValue: (HTMLTextAreaElement, String) -> Unit = { e, v ->
|
||||
e.innerText = v
|
||||
}
|
||||
|
||||
internal val setCheckedValue: (HTMLInputElement, Boolean) -> Unit = { e, v ->
|
||||
e.checked = v
|
||||
saveControlledInputState(e, v)
|
||||
}
|
||||
|
||||
/* Img attributes */
|
||||
|
||||
@@ -19,11 +19,16 @@ import org.w3c.dom.HTMLElement
|
||||
*/
|
||||
open class AttrsBuilder<TElement : Element> : EventsListenerBuilder() {
|
||||
internal val attributesMap = mutableMapOf<String, String>()
|
||||
val styleBuilder = StyleBuilderImpl()
|
||||
internal val styleBuilder = StyleBuilderImpl()
|
||||
|
||||
internal val propertyUpdates = mutableListOf<Pair<(Element, Any) -> Unit, Any>>()
|
||||
internal var refEffect: (DisposableEffectScope.(TElement) -> DisposableEffectResult)? = null
|
||||
|
||||
internal var inputControlledValueSet = false
|
||||
internal var inputDefaultValueSet = false
|
||||
internal var inputControlledCheckedSet = false
|
||||
internal var inputDefaultCheckedSet = false
|
||||
|
||||
/**
|
||||
* [style] add inline CSS-style properties to the element via [StyleBuilder] context
|
||||
*
|
||||
|
||||
@@ -12,10 +12,62 @@ import org.jetbrains.compose.web.events.SyntheticInputEvent
|
||||
import org.jetbrains.compose.web.events.SyntheticSelectEvent
|
||||
import org.w3c.dom.HTMLInputElement
|
||||
|
||||
/**
|
||||
* An extension of [AttrsBuilder].
|
||||
* This class provides a set of methods specific for [Input] element:
|
||||
*
|
||||
* [value] - sets the current input's value.
|
||||
* [defaultValue] - sets the default input's value.
|
||||
*
|
||||
* [checked] - sets the current checked/unchecked state of a checkbox or a radio.
|
||||
* [defaultChecked] - sets the default checked state of a checkbox or a radio.
|
||||
*
|
||||
* [onInvalid] - adds invalid` event listener
|
||||
* [onInput] - adds `input` event listener
|
||||
* [onChange] - adds `change` event listener
|
||||
* [onBeforeInput] - add `beforeinput` event listener
|
||||
* [onSelect] - add `select` event listener
|
||||
*/
|
||||
class InputAttrsBuilder<ValueType>(
|
||||
val inputType: InputType<ValueType>
|
||||
) : AttrsBuilder<HTMLInputElement>() {
|
||||
|
||||
fun value(value: String): InputAttrsBuilder<ValueType> {
|
||||
when (inputType) {
|
||||
InputType.Checkbox,
|
||||
InputType.Radio,
|
||||
InputType.Hidden,
|
||||
InputType.Submit -> attr("value", value)
|
||||
else -> prop(setInputValue, value)
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
fun value(value: Number): InputAttrsBuilder<ValueType> {
|
||||
value(value.toString())
|
||||
return this
|
||||
}
|
||||
|
||||
fun checked(checked: Boolean): InputAttrsBuilder<ValueType> {
|
||||
prop(setCheckedValue, checked)
|
||||
return this
|
||||
}
|
||||
|
||||
fun defaultChecked(): InputAttrsBuilder<ValueType> {
|
||||
attr("checked", "")
|
||||
return this
|
||||
}
|
||||
|
||||
fun defaultValue(value: String): InputAttrsBuilder<ValueType> {
|
||||
attr("value", value)
|
||||
return this
|
||||
}
|
||||
|
||||
fun defaultValue(value: Number): InputAttrsBuilder<ValueType> {
|
||||
attr("value", value.toString())
|
||||
return this
|
||||
}
|
||||
|
||||
fun onInvalid(
|
||||
options: Options = Options.DEFAULT,
|
||||
listener: (SyntheticEvent<HTMLInputElement>) -> Unit
|
||||
@@ -51,3 +103,12 @@ class InputAttrsBuilder<ValueType>(
|
||||
listeners.add(SelectEventListener(options, listener))
|
||||
}
|
||||
}
|
||||
|
||||
internal external interface JsWeakMap {
|
||||
fun delete(key: Any)
|
||||
fun get(key: Any): Any?
|
||||
fun has(key: Any): Boolean
|
||||
fun set(key: Any, value: Any): JsWeakMap
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright 2020-2021 JetBrains s.r.o. and respective authors and developers.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.compose.web.attributes.builders
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.NonRestartableComposable
|
||||
import org.jetbrains.compose.web.attributes.InputType
|
||||
import org.jetbrains.compose.web.dom.ElementScope
|
||||
import org.w3c.dom.HTMLElement
|
||||
import org.w3c.dom.HTMLInputElement
|
||||
import org.w3c.dom.HTMLTextAreaElement
|
||||
|
||||
|
||||
private val controlledInputsValuesWeakMap: JsWeakMap = js("new WeakMap();").unsafeCast<JsWeakMap>()
|
||||
|
||||
internal fun restoreControlledInputState(type: InputType<*>, inputElement: HTMLInputElement) {
|
||||
if (controlledInputsValuesWeakMap.has(inputElement)) {
|
||||
if (type == InputType.Radio) {
|
||||
controlledRadioGroups[inputElement.name]?.forEach { radio ->
|
||||
radio.checked = controlledInputsValuesWeakMap.get(radio).toString().toBoolean()
|
||||
}
|
||||
inputElement.checked = controlledInputsValuesWeakMap.get(inputElement).toString().toBoolean()
|
||||
return
|
||||
}
|
||||
|
||||
if (type == InputType.Checkbox) {
|
||||
inputElement.checked = controlledInputsValuesWeakMap.get(inputElement).toString().toBoolean()
|
||||
} else {
|
||||
inputElement.value = controlledInputsValuesWeakMap.get(inputElement).toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun restoreControlledTextAreaState(element: HTMLTextAreaElement) {
|
||||
if (controlledInputsValuesWeakMap.has(element)) {
|
||||
element.value = controlledInputsValuesWeakMap.get(element).toString()
|
||||
}
|
||||
}
|
||||
|
||||
internal fun <V : Any> saveControlledInputState(element: HTMLElement, value: V) {
|
||||
controlledInputsValuesWeakMap.set(element, value)
|
||||
|
||||
if (element is HTMLInputElement) {
|
||||
updateRadioGroupIfNeeded(element)
|
||||
}
|
||||
}
|
||||
|
||||
// internal only for testing purposes. It actually should be private.
|
||||
internal val controlledRadioGroups = mutableMapOf<String, MutableSet<HTMLInputElement>>()
|
||||
|
||||
private fun updateRadioGroupIfNeeded(element: HTMLInputElement) {
|
||||
if (element.type == "radio" && element.name.isNotEmpty()) {
|
||||
if (!controlledRadioGroups.containsKey(element.name)) {
|
||||
controlledRadioGroups[element.name] = mutableSetOf()
|
||||
}
|
||||
controlledRadioGroups[element.name]!!.add(element)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@NonRestartableComposable
|
||||
internal fun ElementScope<HTMLInputElement>.DisposeRadioGroupEffect() {
|
||||
DisposableRefEffect { ref ->
|
||||
onDispose {
|
||||
controlledRadioGroups[ref.name]?.remove(ref)
|
||||
if (controlledRadioGroups[ref.name]?.isEmpty() == true) {
|
||||
controlledRadioGroups.remove(ref.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,16 @@ import org.w3c.dom.HTMLTextAreaElement
|
||||
|
||||
class TextAreaAttrsBuilder : AttrsBuilder<HTMLTextAreaElement>() {
|
||||
|
||||
fun value(value: String): AttrsBuilder<HTMLTextAreaElement> {
|
||||
prop(setInputValue, value)
|
||||
return this
|
||||
}
|
||||
|
||||
fun defaultValue(value: String): AttrsBuilder<HTMLTextAreaElement> {
|
||||
prop(setTextAreaDefaultValue, value)
|
||||
return this
|
||||
}
|
||||
|
||||
fun onInput(
|
||||
options: Options = Options.DEFAULT,
|
||||
listener: (SyntheticInputEvent<String, HTMLTextAreaElement>) -> Unit
|
||||
|
||||
@@ -2,13 +2,13 @@ package org.jetbrains.compose.web.dom
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.ComposeNode
|
||||
import org.jetbrains.compose.web.attributes.builders.InputAttrsBuilder
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.web.attributes.SelectAttrsBuilder
|
||||
import org.jetbrains.compose.web.attributes.builders.TextAreaAttrsBuilder
|
||||
import org.jetbrains.compose.web.DomApplier
|
||||
import org.jetbrains.compose.web.DomNodeWrapper
|
||||
import kotlinx.browser.document
|
||||
import org.jetbrains.compose.web.attributes.*
|
||||
import org.jetbrains.compose.web.attributes.builders.*
|
||||
import org.jetbrains.compose.web.css.CSSRuleDeclarationList
|
||||
import org.jetbrains.compose.web.css.StyleSheetBuilder
|
||||
import org.jetbrains.compose.web.css.StyleSheetBuilderImpl
|
||||
@@ -650,22 +650,51 @@ fun Section(
|
||||
content = content
|
||||
)
|
||||
|
||||
/**
|
||||
* Adds <textarea> element.
|
||||
* Same as [Input], [TextArea] has two modes: controlled and uncontrolled.
|
||||
*
|
||||
* Controlled mode means that <textarea> value can be changed only by passing a different [value].
|
||||
* Uncontrolled mode means that <textarea> uses its default state management.
|
||||
*
|
||||
* To use controlled mode, simply pass non-null [value].
|
||||
* By default [value] is null and [TextArea] will be in uncontrolled mode.
|
||||
*
|
||||
* Use `defaultValue("some default text")` in uncontrolled mode to set a default text if needed:
|
||||
*
|
||||
* ```
|
||||
* TextArea {
|
||||
* defaultValue("Some Default Text")
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
@Composable
|
||||
fun TextArea(
|
||||
attrs: (TextAreaAttrsBuilder.() -> Unit)? = null,
|
||||
value: String
|
||||
) = TagElement(
|
||||
elementBuilder = TextArea,
|
||||
applyAttrs = {
|
||||
val taab = TextAreaAttrsBuilder()
|
||||
if (attrs != null) {
|
||||
taab.attrs()
|
||||
}
|
||||
taab.value(value)
|
||||
this.copyFrom(taab)
|
||||
}
|
||||
value: String? = null,
|
||||
attrs: (TextAreaAttrsBuilder.() -> Unit)? = null
|
||||
) {
|
||||
Text(value)
|
||||
// if firstProvidedValueWasNotNull then TextArea behaves as controlled input
|
||||
val firstProvidedValueWasNotNull = remember { value != null }
|
||||
|
||||
TagElement(
|
||||
elementBuilder = TextArea,
|
||||
applyAttrs = {
|
||||
val taab = TextAreaAttrsBuilder()
|
||||
if (attrs != null) {
|
||||
taab.attrs()
|
||||
}
|
||||
if (firstProvidedValueWasNotNull) {
|
||||
taab.value(value ?: "")
|
||||
}
|
||||
|
||||
taab.onInput {
|
||||
restoreControlledTextAreaState(it.target)
|
||||
}
|
||||
|
||||
this.copyFrom(taab)
|
||||
},
|
||||
content = null
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
@@ -921,6 +950,37 @@ fun Style(
|
||||
Style(applyAttrs, builder.cssRules)
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds <input> element of [type].
|
||||
*
|
||||
* Input has two modes: controlled and uncontrolled.
|
||||
* Uncontrolled is a default mode. The input's state is managed by [HTMLInputElement] itself.
|
||||
* Controlled mode means that the input's state is managed by compose state.
|
||||
* To use Input in controlled mode, it's required to set its state by calling `value(String|Number)`.
|
||||
*
|
||||
* Consider using [TextInput], [CheckboxInput], [RadioInput], [NumberInput] etc. to use controlled mode.
|
||||
*
|
||||
* Code example of a controlled Input:
|
||||
* ```
|
||||
* val textInputState by remember { mutableStateOf("initial text") }
|
||||
*
|
||||
* Input(type = InputType.Text) {
|
||||
* value(textInputState)
|
||||
* onInput { event ->
|
||||
* textInputState = event.value // without updating the state, the <input> will keep showing an old value
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* Code example of an uncontrolled Input:
|
||||
* ```
|
||||
* Input(type = InputType.Text) {
|
||||
* defaultValue("someDefaultValue") // calling `defaultValue` is optional
|
||||
* // No value set explicitly.
|
||||
* // Whatever typed into the input will be immediately displayed in UI without handling any onInput events.
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
@Composable
|
||||
fun <K> Input(
|
||||
type: InputType<K>,
|
||||
@@ -932,21 +992,22 @@ fun <K> Input(
|
||||
val inputAttrsBuilder = InputAttrsBuilder(type)
|
||||
inputAttrsBuilder.type(type)
|
||||
inputAttrsBuilder.attrs()
|
||||
|
||||
inputAttrsBuilder.onInput {
|
||||
restoreControlledInputState(type = type, inputElement = it.target)
|
||||
}
|
||||
|
||||
this.copyFrom(inputAttrsBuilder)
|
||||
},
|
||||
content = null
|
||||
content = {
|
||||
if (type == InputType.Radio) {
|
||||
DisposeRadioGroupEffect()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun <K> Input(type: InputType<K>) {
|
||||
TagElement(
|
||||
elementBuilder = Input,
|
||||
applyAttrs = {
|
||||
val inputAttrsBuilder = InputAttrsBuilder(type)
|
||||
inputAttrsBuilder.type(type)
|
||||
this.copyFrom(inputAttrsBuilder)
|
||||
},
|
||||
content = null
|
||||
)
|
||||
Input(type) {}
|
||||
}
|
||||
|
||||
@@ -13,54 +13,110 @@ private fun InputAttrsBuilder<String>.applyAttrsWithStringValue(
|
||||
attrs()
|
||||
}
|
||||
|
||||
/**
|
||||
* It's a controlled Input of [InputType.Checkbox].
|
||||
* Controlled input means that its state is always equal [checked] value.
|
||||
* If you need an uncontrolled behaviour, see [Input].
|
||||
*
|
||||
* @see [Input] for more details on controlled and uncontrolled modes.
|
||||
*/
|
||||
@Composable
|
||||
@NonRestartableComposable
|
||||
fun CheckboxInput(checked: Boolean = false, attrs: InputAttrsBuilder<Boolean>.() -> Unit = {}) {
|
||||
Input(
|
||||
type = InputType.Checkbox,
|
||||
attrs = {
|
||||
if (checked) checked()
|
||||
checked(checked)
|
||||
this.attrs()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* It's a controlled Input of [InputType.Date].
|
||||
* Controlled input means that its state is always equal [value].
|
||||
* If you need an uncontrolled behaviour, see [Input].
|
||||
*
|
||||
* @see [Input] for more details on controlled and uncontrolled modes.
|
||||
*/
|
||||
@Composable
|
||||
@NonRestartableComposable
|
||||
fun DateInput(value: String = "", attrs: InputAttrsBuilder<String>.() -> Unit = {}) {
|
||||
Input(type = InputType.Date, attrs = { applyAttrsWithStringValue(value, attrs) })
|
||||
}
|
||||
|
||||
/**
|
||||
* It's a controlled Input of [InputType.DateTimeLocal].
|
||||
* Controlled input means that its state is always equal [value].
|
||||
* If you need an uncontrolled behaviour, see [Input].
|
||||
*
|
||||
* @see [Input] for more details on controlled and uncontrolled modes.
|
||||
*/
|
||||
@Composable
|
||||
@NonRestartableComposable
|
||||
fun DateTimeLocalInput(value: String = "", attrs: InputAttrsBuilder<String>.() -> Unit = {}) {
|
||||
Input(type = InputType.DateTimeLocal, attrs = { applyAttrsWithStringValue(value, attrs) })
|
||||
}
|
||||
|
||||
/**
|
||||
* It's a controlled Input of [InputType.Email].
|
||||
* Controlled input means that its state is always equal [value].
|
||||
* If you need an uncontrolled behaviour, see [Input].
|
||||
*
|
||||
* @see [Input] for more details on controlled and uncontrolled modes.
|
||||
*/
|
||||
@Composable
|
||||
@NonRestartableComposable
|
||||
fun EmailInput(value: String = "", attrs: InputAttrsBuilder<String>.() -> Unit = {}) {
|
||||
Input(type = InputType.Email, attrs = { applyAttrsWithStringValue(value, attrs) })
|
||||
}
|
||||
|
||||
/**
|
||||
* It's a controlled Input of [InputType.File].
|
||||
* Controlled input means that its state is always equal [value].
|
||||
* If you need an uncontrolled behaviour, see [Input].
|
||||
*
|
||||
* @see [Input] for more details on controlled and uncontrolled modes.
|
||||
*/
|
||||
@Composable
|
||||
@NonRestartableComposable
|
||||
fun FileInput(value: String = "", attrs: InputAttrsBuilder<String>.() -> Unit = {}) {
|
||||
Input(type = InputType.File, attrs = { applyAttrsWithStringValue(value, attrs) })
|
||||
}
|
||||
|
||||
/**
|
||||
* It's a controlled Input of [InputType.Hidden].
|
||||
* Controlled input means that its state is always equal [value].
|
||||
* If you need an uncontrolled behaviour, see [Input].
|
||||
*
|
||||
* @see [Input] for more details on controlled and uncontrolled modes.
|
||||
*/
|
||||
@Composable
|
||||
@NonRestartableComposable
|
||||
fun HiddenInput(attrs: InputAttrsBuilder<String>.() -> Unit = {}) {
|
||||
Input(type = InputType.Hidden, attrs = attrs)
|
||||
}
|
||||
|
||||
/**
|
||||
* It's a controlled Input of [InputType.Month].
|
||||
* Controlled input means that its state is always equal [value].
|
||||
* If you need an uncontrolled behaviour, see [Input].
|
||||
*
|
||||
* @see [Input] for more details on controlled and uncontrolled modes.
|
||||
*/
|
||||
@Composable
|
||||
@NonRestartableComposable
|
||||
fun MonthInput(value: String = "", attrs: InputAttrsBuilder<String>.() -> Unit = {}) {
|
||||
Input(type = InputType.Month, attrs = { applyAttrsWithStringValue(value, attrs) })
|
||||
}
|
||||
|
||||
/**
|
||||
* It's a controlled Input of [InputType.Number].
|
||||
* Controlled input means that its state is always equal [value].
|
||||
* If you need an uncontrolled behaviour, see [Input].
|
||||
*
|
||||
* @see [Input] for more details on controlled and uncontrolled modes.
|
||||
*/
|
||||
@Composable
|
||||
@NonRestartableComposable
|
||||
fun NumberInput(
|
||||
@@ -80,24 +136,45 @@ fun NumberInput(
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* It's a controlled Input of [InputType.Password].
|
||||
* Controlled input means that its state is always equal [value].
|
||||
* If you need an uncontrolled behaviour, see [Input].
|
||||
*
|
||||
* @see [Input] for more details on controlled and uncontrolled modes.
|
||||
*/
|
||||
@Composable
|
||||
@NonRestartableComposable
|
||||
fun PasswordInput(value: String = "", attrs: InputAttrsBuilder<String>.() -> Unit = {}) {
|
||||
Input(type = InputType.Password, attrs = { applyAttrsWithStringValue(value, attrs) })
|
||||
}
|
||||
|
||||
/**
|
||||
* It's a controlled Input of [InputType.Radio].
|
||||
* Controlled input means that its state is always equal [checked] value.
|
||||
* If you need an uncontrolled behaviour, see [Input].
|
||||
*
|
||||
* @see [Input] for more details on controlled and uncontrolled modes.
|
||||
*/
|
||||
@Composable
|
||||
@NonRestartableComposable
|
||||
fun RadioInput(checked: Boolean = false, attrs: InputAttrsBuilder<Boolean>.() -> Unit = {}) {
|
||||
Input(
|
||||
type = InputType.Radio,
|
||||
attrs = {
|
||||
if (checked) checked()
|
||||
checked(checked)
|
||||
attrs()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* It's a controlled Input of [InputType.Range].
|
||||
* Controlled input means that its state is always equal [value].
|
||||
* If you need an uncontrolled behaviour, see [Input].
|
||||
*
|
||||
* @see [Input] for more details on controlled and uncontrolled modes.
|
||||
*/
|
||||
@Composable
|
||||
@NonRestartableComposable
|
||||
fun RangeInput(
|
||||
@@ -119,42 +196,88 @@ fun RangeInput(
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* It's a controlled Input of [InputType.Search].
|
||||
* Controlled input means that its state is always equal [value].
|
||||
* If you need an uncontrolled behaviour, see [Input].
|
||||
*
|
||||
* @see [Input] for more details on controlled and uncontrolled modes.
|
||||
*/
|
||||
@Composable
|
||||
@NonRestartableComposable
|
||||
fun SearchInput(value: String = "", attrs: InputAttrsBuilder<String>.() -> Unit = {}) {
|
||||
Input(type = InputType.Search, attrs = { applyAttrsWithStringValue(value, attrs) })
|
||||
}
|
||||
|
||||
/**
|
||||
* It's a controlled Input of [InputType.Submit].
|
||||
* If you need an uncontrolled behaviour, see [Input].
|
||||
*/
|
||||
@Composable
|
||||
@NonRestartableComposable
|
||||
fun SubmitInput(attrs: InputAttrsBuilder<Unit>.() -> Unit = {}) {
|
||||
Input(type = InputType.Submit, attrs = attrs)
|
||||
}
|
||||
|
||||
/**
|
||||
* It's a controlled Input of [InputType.Tel].
|
||||
* Controlled input means that its state is always equal [value].
|
||||
* If you need an uncontrolled behaviour, see [Input].
|
||||
*
|
||||
* @see [Input] for more details on controlled and uncontrolled modes.
|
||||
*/
|
||||
@Composable
|
||||
@NonRestartableComposable
|
||||
fun TelInput(value: String = "", attrs: InputAttrsBuilder<String>.() -> Unit = {}) {
|
||||
Input(type = InputType.Tel, attrs = { applyAttrsWithStringValue(value, attrs) })
|
||||
}
|
||||
|
||||
/**
|
||||
* It's a controlled Input of [InputType.Text].
|
||||
* Controlled input means that its state is always equal [value].
|
||||
* If you need an uncontrolled behaviour, see [Input].
|
||||
*
|
||||
* @see [Input] for more details on controlled and uncontrolled modes.
|
||||
*/
|
||||
@Composable
|
||||
@NonRestartableComposable
|
||||
fun TextInput(value: String = "", attrs: InputAttrsBuilder<String>.() -> Unit = {}) {
|
||||
Input(type = InputType.Text, attrs = { applyAttrsWithStringValue(value, attrs) })
|
||||
}
|
||||
|
||||
/**
|
||||
* It's a controlled Input of [InputType.Time].
|
||||
* Controlled input means that its state is always equal [value].
|
||||
* If you need an uncontrolled behaviour, see [Input].
|
||||
*
|
||||
* @see [Input] for more details on controlled and uncontrolled modes.
|
||||
*/
|
||||
@Composable
|
||||
@NonRestartableComposable
|
||||
fun TimeInput(value: String = "", attrs: InputAttrsBuilder<String>.() -> Unit = {}) {
|
||||
Input(type = InputType.Time, attrs = { applyAttrsWithStringValue(value, attrs) })
|
||||
}
|
||||
|
||||
/**
|
||||
* It's a controlled Input of [InputType.Url].
|
||||
* Controlled input means that its state is always equal [value].
|
||||
* If you need an uncontrolled behaviour, see [Input].
|
||||
*
|
||||
* @see [Input] for more details on controlled and uncontrolled modes.
|
||||
*/
|
||||
@Composable
|
||||
@NonRestartableComposable
|
||||
fun UrlInput(value: String = "", attrs: InputAttrsBuilder<String>.() -> Unit = {}) {
|
||||
Input(type = InputType.Url, attrs = { applyAttrsWithStringValue(value, attrs) })
|
||||
}
|
||||
|
||||
/**
|
||||
* It's a controlled Input of [InputType.Week].
|
||||
* Controlled input means that its state is always equal [value].
|
||||
* If you need an uncontrolled behaviour, see [Input].
|
||||
*
|
||||
* @see [Input] for more details on controlled and uncontrolled modes.
|
||||
*/
|
||||
@Composable
|
||||
@NonRestartableComposable
|
||||
fun WeekInput(value: String = "", attrs: InputAttrsBuilder<String>.() -> Unit = {}) {
|
||||
|
||||
108
web/core/src/jsTest/kotlin/ControlledRadioGroupsTests.kt
Normal file
108
web/core/src/jsTest/kotlin/ControlledRadioGroupsTests.kt
Normal file
@@ -0,0 +1,108 @@
|
||||
package org.jetbrains.compose.web.core.tests
|
||||
|
||||
import androidx.compose.runtime.*
|
||||
import org.jetbrains.compose.web.dom.Div
|
||||
import org.jetbrains.compose.web.attributes.builders.controlledRadioGroups
|
||||
import org.jetbrains.compose.web.attributes.name
|
||||
import org.jetbrains.compose.web.dom.RadioInput
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class ControlledRadioGroupsTests {
|
||||
|
||||
@Test
|
||||
fun controlledRadioGroupGetsUpdated() = runTest {
|
||||
var countOfRadio by mutableStateOf(0)
|
||||
|
||||
composition {
|
||||
repeat(countOfRadio) {
|
||||
key (it) {
|
||||
RadioInput(checked = false) {
|
||||
id("r$it")
|
||||
name("group1")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assertEquals(0, controlledRadioGroups.size)
|
||||
|
||||
countOfRadio = 5
|
||||
waitChanges()
|
||||
|
||||
assertEquals(1, controlledRadioGroups.size)
|
||||
assertTrue(controlledRadioGroups.keys.first() == "group1")
|
||||
assertEquals(5, controlledRadioGroups["group1"]!!.size)
|
||||
|
||||
countOfRadio = 2
|
||||
waitChanges()
|
||||
|
||||
assertEquals(1, controlledRadioGroups.size)
|
||||
assertTrue(controlledRadioGroups.keys.first() == "group1")
|
||||
assertEquals(2, controlledRadioGroups["group1"]!!.size)
|
||||
|
||||
countOfRadio = 0
|
||||
waitChanges()
|
||||
|
||||
assertEquals(0, controlledRadioGroups.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun multipleControlledRadioGroupsGetUpdated() = runTest {
|
||||
var countOfRadioG1 by mutableStateOf(0)
|
||||
var countOfRadioG2 by mutableStateOf(0)
|
||||
|
||||
composition {
|
||||
Div {
|
||||
repeat(countOfRadioG1) {
|
||||
key(it) {
|
||||
RadioInput(checked = false) {
|
||||
id("r1-$it")
|
||||
name("group1")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Div {
|
||||
repeat(countOfRadioG2) {
|
||||
key(it) {
|
||||
RadioInput(checked = false) {
|
||||
id("r2-$it")
|
||||
name("group2")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assertEquals(0, controlledRadioGroups.size)
|
||||
|
||||
countOfRadioG1 = 5
|
||||
countOfRadioG2 = 10
|
||||
waitChanges()
|
||||
|
||||
assertEquals(2, controlledRadioGroups.size)
|
||||
assertEquals(5, controlledRadioGroups["group1"]!!.size)
|
||||
assertEquals(10, controlledRadioGroups["group2"]!!.size)
|
||||
|
||||
countOfRadioG2 = 2
|
||||
waitChanges()
|
||||
|
||||
assertEquals(2, controlledRadioGroups.size)
|
||||
assertEquals(5, controlledRadioGroups["group1"]!!.size)
|
||||
assertEquals(2, controlledRadioGroups["group2"]!!.size)
|
||||
|
||||
countOfRadioG1 = 0
|
||||
waitChanges()
|
||||
|
||||
assertEquals(1, controlledRadioGroups.size)
|
||||
assertEquals(2, controlledRadioGroups["group2"]!!.size)
|
||||
|
||||
countOfRadioG2 = 0
|
||||
waitChanges()
|
||||
|
||||
assertEquals(0, controlledRadioGroups.size)
|
||||
}
|
||||
}
|
||||
@@ -81,11 +81,10 @@ class EventTests {
|
||||
|
||||
composition {
|
||||
TextArea(
|
||||
{
|
||||
onInput { handled = true }
|
||||
},
|
||||
value = ""
|
||||
)
|
||||
) {
|
||||
onInput { handled = true }
|
||||
}
|
||||
}
|
||||
|
||||
val radio = root.firstChild as HTMLTextAreaElement
|
||||
|
||||
@@ -4,9 +4,11 @@ import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.runtime.getValue
|
||||
import org.jetbrains.compose.web.attributes.*
|
||||
import org.jetbrains.compose.web.core.tests.asHtmlElement
|
||||
import org.jetbrains.compose.web.core.tests.runTest
|
||||
import org.jetbrains.compose.web.core.tests.waitForAnimationFrame
|
||||
import org.jetbrains.compose.web.dom.*
|
||||
import org.jetbrains.compose.web.renderComposable
|
||||
import org.w3c.dom.HTMLInputElement
|
||||
import org.w3c.dom.HTMLTextAreaElement
|
||||
import kotlin.test.Test
|
||||
@@ -405,9 +407,9 @@ class InputsGenerateCorrectHtmlTests {
|
||||
@Test
|
||||
fun textAreaWithAutoComplete() = runTest {
|
||||
composition {
|
||||
TextArea({
|
||||
TextArea(attrs = {
|
||||
autoComplete(AutoComplete.email)
|
||||
}, value = "")
|
||||
})
|
||||
}
|
||||
assertEquals("""<textarea autocomplete="email"></textarea>""", root.innerHTML)
|
||||
}
|
||||
@@ -503,4 +505,18 @@ class InputsGenerateCorrectHtmlTests {
|
||||
|
||||
assertEquals("text", (root.firstChild as HTMLTextAreaElement).value)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun textAreaWithDefaultValueAndWithoutIt() {
|
||||
val root = "div".asHtmlElement()
|
||||
|
||||
renderComposable(root = root) {
|
||||
TextArea()
|
||||
TextArea {
|
||||
defaultValue("not-empty-default-value")
|
||||
}
|
||||
}
|
||||
|
||||
assertEquals("<textarea></textarea><textarea>not-empty-default-value</textarea>", root.innerHTML)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,27 +8,18 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import org.jetbrains.compose.web.attributes.Draggable
|
||||
import org.jetbrains.compose.web.attributes.InputType
|
||||
import org.jetbrains.compose.web.attributes.name
|
||||
import org.jetbrains.compose.web.css.selectors.className
|
||||
import org.jetbrains.compose.web.css.selectors.hover
|
||||
import org.jetbrains.compose.web.css.selectors.plus
|
||||
import org.jetbrains.compose.web.dom.A
|
||||
import org.jetbrains.compose.web.dom.Button
|
||||
import org.jetbrains.compose.web.dom.Div
|
||||
import org.jetbrains.compose.web.dom.Input
|
||||
import org.jetbrains.compose.web.dom.Style
|
||||
import org.jetbrains.compose.web.dom.Text
|
||||
import org.jetbrains.compose.web.dom.TextArea
|
||||
import org.jetbrains.compose.web.renderComposableInBody
|
||||
import org.jetbrains.compose.web.sample.tests.launchTestCase
|
||||
import kotlinx.browser.window
|
||||
import kotlinx.coroutines.MainScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import org.jetbrains.compose.web.attributes.value
|
||||
import org.jetbrains.compose.web.attributes.*
|
||||
import org.jetbrains.compose.web.css.*
|
||||
import org.jetbrains.compose.web.dom.*
|
||||
import org.w3c.dom.url.URLSearchParams
|
||||
|
||||
class State {
|
||||
@@ -143,6 +134,45 @@ fun main() {
|
||||
println("renderComposable")
|
||||
val counter = remember { mutableStateOf(0) }
|
||||
|
||||
CheckboxInput(checked = false) {
|
||||
onInput {
|
||||
println("Checkbox input = ${it.value}")
|
||||
}
|
||||
onChange {
|
||||
println("Checkbox onChange = ${it.value}")
|
||||
}
|
||||
}
|
||||
|
||||
var emailState by remember { mutableStateOf("") }
|
||||
var rangeState by remember { mutableStateOf<Number>(10) }
|
||||
|
||||
TextInput(value = emailState) {
|
||||
onInput {
|
||||
println("Typed value = ${it.value}")
|
||||
emailState = it.value
|
||||
}
|
||||
}
|
||||
|
||||
NumberInput(value = 10) {
|
||||
onBeforeInput { println(("number onBeforeInput = ${it.value}")) }
|
||||
onInput { println(("number onInput = ${it.value}")) }
|
||||
onChange { println(("number onChange = ${it.value}")) }
|
||||
}
|
||||
|
||||
RangeInput(rangeState) {
|
||||
onBeforeInput { println(("RangeInput onBeforeInput = ${it.value}")) }
|
||||
onInput {
|
||||
println(("RangeInput onInput = ${it.value}"))
|
||||
rangeState = it.value ?: 0
|
||||
}
|
||||
}
|
||||
|
||||
MonthInput(value = "2021-10") {
|
||||
onInput {
|
||||
println("Month = ${it.value}")
|
||||
}
|
||||
}
|
||||
|
||||
CounterApp(counter)
|
||||
|
||||
val inputValue = remember { mutableStateOf("") }
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package org.jetbrains.compose.web.sample.tests
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.web.sample.tests.ControlledInputsTests
|
||||
import androidx.compose.web.sample.tests.SelectElementTests
|
||||
import androidx.compose.web.sample.tests.UncontrolledInputsTests
|
||||
import org.jetbrains.compose.web.dom.Span
|
||||
import org.jetbrains.compose.web.dom.Text
|
||||
import org.jetbrains.compose.web.renderComposableInBody
|
||||
@@ -33,7 +35,10 @@ internal val testCases = mutableMapOf<String, TestCase>()
|
||||
|
||||
fun launchTestCase(testCaseId: String) {
|
||||
// this makes test cases get initialised:
|
||||
listOf<Any>(TestCases1(), InputsTests(), EventsTests(), SelectElementTests())
|
||||
listOf<Any>(
|
||||
TestCases1(), InputsTests(), EventsTests(),
|
||||
SelectElementTests(), ControlledInputsTests(), UncontrolledInputsTests()
|
||||
)
|
||||
|
||||
if (testCaseId !in testCases) error("Test Case '$testCaseId' not found")
|
||||
|
||||
|
||||
@@ -0,0 +1,412 @@
|
||||
/*
|
||||
* Copyright 2020-2021 JetBrains s.r.o. and respective authors and developers.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package androidx.compose.web.sample.tests
|
||||
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import org.jetbrains.compose.web.attributes.InputType
|
||||
import org.jetbrains.compose.web.attributes.name
|
||||
import org.jetbrains.compose.web.dom.*
|
||||
import org.jetbrains.compose.web.sample.tests.TestText
|
||||
import org.jetbrains.compose.web.sample.tests.testCase
|
||||
|
||||
class ControlledInputsTests {
|
||||
|
||||
val textInputHardcodedValueShouldNotChange by testCase {
|
||||
var onInputText by remember { mutableStateOf("None") }
|
||||
|
||||
P { TestText(onInputText) }
|
||||
|
||||
Div {
|
||||
TextInput(value = "hardcoded", attrs = {
|
||||
id("textInput")
|
||||
onInput {
|
||||
onInputText = it.value
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
val textInputMutableValueShouldGetOverridden by testCase {
|
||||
var onInputText by remember { mutableStateOf("InitialValue") }
|
||||
|
||||
P { TestText(onInputText) }
|
||||
|
||||
Div {
|
||||
TextInput(value = onInputText, attrs = {
|
||||
id("textInput")
|
||||
onInput {
|
||||
onInputText = "OVERRIDDEN VALUE"
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
val textInputMutableValueShouldChange by testCase {
|
||||
var onInputText by remember { mutableStateOf("InitialValue") }
|
||||
|
||||
P { TestText(onInputText) }
|
||||
|
||||
Div {
|
||||
TextInput(value = onInputText, attrs = {
|
||||
id("textInput")
|
||||
onInput {
|
||||
onInputText = it.value
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
val textAreaHardcodedValueShouldNotChange by testCase {
|
||||
var onInputText by remember { mutableStateOf("None") }
|
||||
|
||||
P { TestText(onInputText) }
|
||||
|
||||
Div {
|
||||
TextArea(value = "hardcoded", attrs = {
|
||||
id("textArea")
|
||||
onInput {
|
||||
onInputText = it.value
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
val textAreaMutableValueShouldGetOverridden by testCase {
|
||||
var onInputText by remember { mutableStateOf("InitialValue") }
|
||||
|
||||
P { TestText(onInputText) }
|
||||
|
||||
Div {
|
||||
TextArea(value = onInputText, attrs = {
|
||||
id("textArea")
|
||||
onInput {
|
||||
onInputText = "OVERRIDDEN VALUE"
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
val textAreaMutableValueShouldChange by testCase {
|
||||
var onInputText by remember { mutableStateOf("InitialValue") }
|
||||
|
||||
P { TestText(onInputText) }
|
||||
|
||||
Div {
|
||||
TextArea(value = onInputText, attrs = {
|
||||
id("textArea")
|
||||
onInput {
|
||||
onInputText = it.value
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
val checkBoxHardcodedNeverChanges by testCase {
|
||||
var checkClicked by remember { mutableStateOf(false) }
|
||||
|
||||
P { TestText(checkClicked.toString()) }
|
||||
|
||||
Div {
|
||||
CheckboxInput(checked = false) {
|
||||
id("checkbox")
|
||||
onInput {
|
||||
checkClicked = it.value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val checkBoxMutableValueChanges by testCase {
|
||||
var checked by remember { mutableStateOf(false) }
|
||||
|
||||
P { TestText(checked.toString()) }
|
||||
|
||||
Div {
|
||||
CheckboxInput(checked = checked) {
|
||||
id("checkbox")
|
||||
onInput {
|
||||
checked = it.value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val checkBoxDefaultCheckedChangesDoesntAffectState by testCase {
|
||||
var checked by remember { mutableStateOf(true) }
|
||||
|
||||
P { TestText(checked.toString()) }
|
||||
|
||||
Div {
|
||||
Input(type = InputType.Checkbox) {
|
||||
id("checkboxMirror")
|
||||
if (checked) defaultChecked()
|
||||
}
|
||||
|
||||
Input(type = InputType.Checkbox) {
|
||||
id("checkboxMain")
|
||||
checked(checked)
|
||||
onInput { checked = it.value }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val radioHardcodedNeverChanges by testCase {
|
||||
Div {
|
||||
RadioInput(checked = true) {
|
||||
id("radio1")
|
||||
name("group1")
|
||||
}
|
||||
RadioInput(checked = false) {
|
||||
id("radio2")
|
||||
name("group1")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val radioMutableCheckedChanges by testCase {
|
||||
var checked by remember { mutableStateOf(0) }
|
||||
|
||||
TestText("Checked - $checked")
|
||||
|
||||
Div {
|
||||
RadioInput(checked = checked == 1) {
|
||||
id("radio1")
|
||||
name("group1")
|
||||
onInput { checked = 1 }
|
||||
}
|
||||
RadioInput(checked = checked == 2) {
|
||||
id("radio2")
|
||||
name("group1")
|
||||
onInput { checked = 2 }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val numberHardcodedNeverChanges by testCase {
|
||||
var typedValue by remember { mutableStateOf("None") }
|
||||
TestText(value = typedValue)
|
||||
|
||||
NumberInput(value = 5, min = 0, max = 100) {
|
||||
id("numberInput")
|
||||
onInput {
|
||||
typedValue = it.value.toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val numberMutableChanges by testCase {
|
||||
var value by remember { mutableStateOf(5) }
|
||||
TestText(value = value.toString())
|
||||
|
||||
NumberInput(value = value, min = 0, max = 100) {
|
||||
id("numberInput")
|
||||
onInput {
|
||||
value = it.value!!.toInt()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val rangeHardcodedNeverChanges by testCase {
|
||||
var typedValue by remember { mutableStateOf("None") }
|
||||
|
||||
TestText(value = typedValue)
|
||||
|
||||
RangeInput(value = 21) {
|
||||
id("rangeInput")
|
||||
onInput {
|
||||
typedValue = it.value.toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val rangeMutableChanges by testCase {
|
||||
var value by remember { mutableStateOf(10) }
|
||||
|
||||
TestText(value = value.toString())
|
||||
|
||||
RangeInput(value = value) {
|
||||
id("rangeInput")
|
||||
onInput {
|
||||
value = it.value!!.toInt()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val emailHardcodedNeverChanges by testCase {
|
||||
var typedValue by remember { mutableStateOf("None") }
|
||||
TestText(value = typedValue)
|
||||
|
||||
EmailInput(value = "a@a.abc") {
|
||||
id("emailInput")
|
||||
onInput {
|
||||
typedValue = it.value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val emailMutableChanges by testCase {
|
||||
var value by remember { mutableStateOf("") }
|
||||
TestText(value = value)
|
||||
|
||||
EmailInput(value = value) {
|
||||
id("emailInput")
|
||||
onInput {
|
||||
value = it.value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val passwordHardcodedNeverChanges by testCase {
|
||||
var typeValue by remember { mutableStateOf("None") }
|
||||
TestText(value = typeValue)
|
||||
|
||||
PasswordInput(value = "123456") {
|
||||
id("passwordInput")
|
||||
onInput {
|
||||
typeValue = it.value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val passwordMutableChanges by testCase {
|
||||
var value by remember { mutableStateOf("") }
|
||||
TestText(value = value)
|
||||
|
||||
EmailInput(value = value) {
|
||||
id("passwordInput")
|
||||
onInput {
|
||||
value = it.value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val searchHardcodedNeverChanges by testCase {
|
||||
var typeValue by remember { mutableStateOf("None") }
|
||||
TestText(value = typeValue)
|
||||
|
||||
SearchInput(value = "hardcoded") {
|
||||
id("searchInput")
|
||||
onInput {
|
||||
typeValue = it.value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val searchMutableChanges by testCase {
|
||||
var typeValue by remember { mutableStateOf("") }
|
||||
TestText(value = typeValue)
|
||||
|
||||
SearchInput(value = typeValue) {
|
||||
id("searchInput")
|
||||
onInput {
|
||||
typeValue = it.value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val telHardcodedNeverChanges by testCase {
|
||||
var typedValue by remember { mutableStateOf("None") }
|
||||
TestText(value = typedValue)
|
||||
|
||||
TelInput(value = "123456") {
|
||||
id("telInput")
|
||||
onInput {
|
||||
typedValue = it.value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val telMutableChanges by testCase {
|
||||
var value by remember { mutableStateOf("") }
|
||||
TestText(value = value)
|
||||
|
||||
TelInput(value = value) {
|
||||
id("telInput")
|
||||
onInput {
|
||||
value = it.value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val urlHardcodedNeverChanges by testCase {
|
||||
var typedValue by remember { mutableStateOf("None") }
|
||||
TestText(value = typedValue)
|
||||
|
||||
UrlInput(value = "www.site.com") {
|
||||
id("urlInput")
|
||||
onInput {
|
||||
typedValue = it.value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val urlMutableChanges by testCase {
|
||||
var value by remember { mutableStateOf("") }
|
||||
TestText(value = value)
|
||||
|
||||
UrlInput(value = value) {
|
||||
id("urlInput")
|
||||
onInput {
|
||||
value = it.value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val hardcodedDateInputNeverChanges by testCase {
|
||||
var inputValue by remember { mutableStateOf("None") }
|
||||
|
||||
TestText(inputValue)
|
||||
|
||||
DateInput(value = "") {
|
||||
id("dateInput")
|
||||
onInput {
|
||||
inputValue = "onInput Caught"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val mutableDateInputChanges by testCase {
|
||||
var inputValue by remember { mutableStateOf("") }
|
||||
|
||||
TestText(inputValue)
|
||||
|
||||
DateInput(value = inputValue) {
|
||||
id("dateInput")
|
||||
onInput {
|
||||
inputValue = it.value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val hardcodedTimeNeverChanges by testCase {
|
||||
var typedValue by remember { mutableStateOf("None") }
|
||||
|
||||
TestText(typedValue)
|
||||
|
||||
TimeInput(value = "14:00") {
|
||||
id("time")
|
||||
onInput {
|
||||
typedValue = "onInput Caught"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val mutableTimeChanges by testCase {
|
||||
var value by remember { mutableStateOf("") }
|
||||
|
||||
TestText(value)
|
||||
|
||||
TimeInput(value = value) {
|
||||
id("time")
|
||||
onInput {
|
||||
value = it.value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,6 @@ class InputsTests {
|
||||
TestText(value = state)
|
||||
|
||||
TextArea(
|
||||
value = state,
|
||||
attrs = {
|
||||
id("input")
|
||||
onInput { state = it.value }
|
||||
@@ -23,14 +22,14 @@ class InputsTests {
|
||||
}
|
||||
|
||||
val textInputGetsPrinted by testCase {
|
||||
var state by remember { mutableStateOf("") }
|
||||
var state by remember { mutableStateOf("Initial-") }
|
||||
|
||||
TestText(value = state)
|
||||
|
||||
Input(
|
||||
type = InputType.Text,
|
||||
attrs = {
|
||||
value(state)
|
||||
defaultValue(state)
|
||||
id("input")
|
||||
onInput { state = it.value }
|
||||
}
|
||||
@@ -46,9 +45,7 @@ class InputsTests {
|
||||
type = InputType.Checkbox,
|
||||
attrs = {
|
||||
id("checkbox")
|
||||
if (checked) {
|
||||
checked()
|
||||
}
|
||||
checked(checked)
|
||||
onInput { checked = !checked }
|
||||
}
|
||||
)
|
||||
@@ -227,7 +224,8 @@ class InputsTests {
|
||||
P { TestText(state) }
|
||||
|
||||
Div {
|
||||
TextArea(value = state, attrs = {
|
||||
TextArea(attrs = {
|
||||
defaultValue(state)
|
||||
id("textArea")
|
||||
onChange { state = it.value }
|
||||
})
|
||||
@@ -243,7 +241,7 @@ class InputsTests {
|
||||
|
||||
|
||||
Div {
|
||||
TextInput(value = "", attrs = {
|
||||
TextInput(value = inputState, attrs = {
|
||||
id("textInput")
|
||||
onBeforeInput {
|
||||
state = it.data ?: ""
|
||||
@@ -264,7 +262,7 @@ class InputsTests {
|
||||
|
||||
|
||||
Div {
|
||||
TextArea(value = "", attrs = {
|
||||
TextArea(attrs = {
|
||||
id("textArea")
|
||||
onBeforeInput {
|
||||
state = it.data ?: ""
|
||||
|
||||
@@ -0,0 +1,190 @@
|
||||
/*
|
||||
* Copyright 2020-2021 JetBrains s.r.o. and respective authors and developers.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package androidx.compose.web.sample.tests
|
||||
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import org.jetbrains.compose.web.attributes.InputType
|
||||
import org.jetbrains.compose.web.attributes.name
|
||||
import org.jetbrains.compose.web.dom.*
|
||||
import org.jetbrains.compose.web.sample.tests.TestText
|
||||
import org.jetbrains.compose.web.sample.tests.testCase
|
||||
|
||||
class UncontrolledInputsTests {
|
||||
|
||||
val textInputDefaultValueRemainsTheSameButValueCanBeChanged by testCase {
|
||||
var inputValue by remember { mutableStateOf("") }
|
||||
|
||||
Input(type = InputType.Text) {
|
||||
|
||||
id("textInput")
|
||||
defaultValue("defaultInputValue")
|
||||
|
||||
attr("data-input-value", inputValue)
|
||||
|
||||
onInput {
|
||||
inputValue = it.value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val textAreaDefaultValueRemainsTheSameButValueCanBeChanged by testCase {
|
||||
var inputValue by remember { mutableStateOf("") }
|
||||
|
||||
TextArea {
|
||||
|
||||
id("textArea")
|
||||
defaultValue("defaultTextAreaValue")
|
||||
|
||||
attr("data-text-area-value", inputValue)
|
||||
|
||||
onInput {
|
||||
inputValue = it.value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val checkBoxDefaultCheckedRemainsTheSameButCheckedCanBeChanged by testCase {
|
||||
var checkedValue by remember { mutableStateOf(true) }
|
||||
|
||||
Input(type = InputType.Checkbox) {
|
||||
id("checkbox")
|
||||
defaultChecked()
|
||||
value("checkbox-value")
|
||||
|
||||
attr("data-checkbox", checkedValue.toString())
|
||||
|
||||
onInput {
|
||||
checkedValue = it.value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val radioDefaultCheckedRemainsTheSameButCheckedCanBeChanged by testCase {
|
||||
var checkedValue by remember { mutableStateOf("") }
|
||||
|
||||
Input(type = InputType.Radio) {
|
||||
id("radio1")
|
||||
defaultChecked()
|
||||
value("radio-value1")
|
||||
name("radiogroup")
|
||||
|
||||
attr("data-radio", checkedValue)
|
||||
|
||||
onInput {
|
||||
checkedValue = "radio-value1"
|
||||
}
|
||||
}
|
||||
|
||||
Input(type = InputType.Radio) {
|
||||
id("radio2")
|
||||
value("radio-value2")
|
||||
name("radiogroup")
|
||||
|
||||
attr("data-radio", checkedValue)
|
||||
|
||||
onInput {
|
||||
checkedValue = "radio-value2"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val numberDefaultValueRemainsTheSameButValueCanBeChanged by testCase {
|
||||
var typedValue by remember { mutableStateOf("None") }
|
||||
|
||||
TestText(value = "Value = $typedValue")
|
||||
|
||||
Input(type = InputType.Number) {
|
||||
id("numberInput")
|
||||
defaultValue(11)
|
||||
onInput {
|
||||
typedValue = it.value.toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val rangeDefaultValueRemainsTheSameButValueCanBeChanged by testCase {
|
||||
var typedValue by remember { mutableStateOf("None") }
|
||||
|
||||
TestText(value = "Value = $typedValue")
|
||||
|
||||
Input(type = InputType.Range) {
|
||||
id("rangeInput")
|
||||
defaultValue(7)
|
||||
onInput {
|
||||
typedValue = it.value.toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val emailDefaultValueRemainsTheSameButValueCanBeChanged by testCase {
|
||||
var typedValue by remember { mutableStateOf("None") }
|
||||
TestText(value = "Value = $typedValue")
|
||||
|
||||
Input(type = InputType.Email) {
|
||||
id("emailInput")
|
||||
defaultValue("a@a.abc")
|
||||
onInput {
|
||||
typedValue = it.value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val passwordDefaultValueRemainsTheSameButValueCanBeChanged by testCase {
|
||||
var typedValue by remember { mutableStateOf("None") }
|
||||
TestText(value = "Value = $typedValue")
|
||||
|
||||
Input(type = InputType.Password) {
|
||||
id("passwordInput")
|
||||
defaultValue("1111")
|
||||
onInput {
|
||||
typedValue = it.value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val searchDefaultValueRemainsTheSameButValueCanBeChanged by testCase {
|
||||
var typedValue by remember { mutableStateOf("None") }
|
||||
TestText(value = "Value = $typedValue")
|
||||
|
||||
Input(type = InputType.Search) {
|
||||
id("searchInput")
|
||||
defaultValue("kotlin")
|
||||
onInput {
|
||||
typedValue = it.value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val telDefaultValueRemainsTheSameButValueCanBeChanged by testCase {
|
||||
var typedValue by remember { mutableStateOf("None") }
|
||||
TestText(value = typedValue)
|
||||
|
||||
Input(type = InputType.Tel) {
|
||||
id("telInput")
|
||||
defaultValue("123123")
|
||||
onInput {
|
||||
typedValue = it.value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val urlDefaultValueRemainsTheSameButValueCanBeChanged by testCase {
|
||||
var typedValue by remember { mutableStateOf("None") }
|
||||
TestText(value = typedValue)
|
||||
|
||||
Input(type = InputType.Url) {
|
||||
id("urlInput")
|
||||
defaultValue("www.site.com")
|
||||
onInput {
|
||||
typedValue = it.value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,507 @@
|
||||
/*
|
||||
* Copyright 2020-2021 JetBrains s.r.o. and respective authors and developers.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.compose.web.tests.integration
|
||||
|
||||
import org.jetbrains.compose.web.tests.integration.common.BaseIntegrationTests
|
||||
import org.jetbrains.compose.web.tests.integration.common.ResolveDrivers
|
||||
import org.jetbrains.compose.web.tests.integration.common.openTestPage
|
||||
import org.jetbrains.compose.web.tests.integration.common.waitTextToBe
|
||||
import org.junit.jupiter.api.Assumptions
|
||||
import org.openqa.selenium.By
|
||||
import org.openqa.selenium.Keys
|
||||
import org.openqa.selenium.WebDriver
|
||||
import org.openqa.selenium.chrome.ChromeDriver
|
||||
|
||||
class ControlledInputsTests : BaseIntegrationTests() {
|
||||
|
||||
@ResolveDrivers
|
||||
fun textInputHardcodedValueShouldNotChange(driver: WebDriver) {
|
||||
driver.openTestPage("textInputHardcodedValueShouldNotChange")
|
||||
driver.waitTextToBe(value = "None")
|
||||
|
||||
val controlledTextInput = driver.findElement(By.id("textInput"))
|
||||
|
||||
controlledTextInput.sendKeys("A")
|
||||
driver.waitTextToBe(value = "hardcodedA")
|
||||
|
||||
controlledTextInput.sendKeys("B")
|
||||
driver.waitTextToBe(value = "hardcodedB")
|
||||
|
||||
controlledTextInput.sendKeys("C")
|
||||
driver.waitTextToBe(value = "hardcodedC")
|
||||
|
||||
check(controlledTextInput.getAttribute("value") == "hardcoded")
|
||||
}
|
||||
|
||||
@ResolveDrivers
|
||||
fun textInputMutableValueShouldGetOverridden(driver: WebDriver) {
|
||||
driver.openTestPage("textInputMutableValueShouldGetOverridden")
|
||||
driver.waitTextToBe(value = "InitialValue")
|
||||
|
||||
val controlledTextInput = driver.findElement(By.id("textInput"))
|
||||
controlledTextInput.sendKeys("ABC")
|
||||
|
||||
driver.waitTextToBe(value = "OVERRIDDEN VALUE")
|
||||
check(controlledTextInput.getAttribute("value") == "OVERRIDDEN VALUE")
|
||||
}
|
||||
|
||||
@ResolveDrivers
|
||||
fun textInputMutableValueShouldChange(driver: WebDriver) {
|
||||
driver.openTestPage("textInputMutableValueShouldChange")
|
||||
driver.waitTextToBe(value = "InitialValue")
|
||||
|
||||
val controlledTextInput = driver.findElement(By.id("textInput"))
|
||||
|
||||
controlledTextInput.sendKeys("A")
|
||||
driver.waitTextToBe(value = "InitialValueA")
|
||||
|
||||
controlledTextInput.sendKeys("B")
|
||||
driver.waitTextToBe(value = "InitialValueAB")
|
||||
|
||||
controlledTextInput.sendKeys("C")
|
||||
driver.waitTextToBe(value = "InitialValueABC")
|
||||
|
||||
check(controlledTextInput.getAttribute("value") == "InitialValueABC")
|
||||
}
|
||||
|
||||
@ResolveDrivers
|
||||
fun textAreaHardcodedValueShouldNotChange(driver: WebDriver) {
|
||||
driver.openTestPage("textAreaHardcodedValueShouldNotChange")
|
||||
driver.waitTextToBe(value = "None")
|
||||
|
||||
val controlledTextArea = driver.findElement(By.id("textArea"))
|
||||
|
||||
controlledTextArea.sendKeys("A")
|
||||
driver.waitTextToBe(value = "hardcodedA")
|
||||
|
||||
controlledTextArea.sendKeys("B")
|
||||
driver.waitTextToBe(value = "hardcodedB")
|
||||
|
||||
controlledTextArea.sendKeys("C")
|
||||
driver.waitTextToBe(value = "hardcodedC")
|
||||
|
||||
check(controlledTextArea.getAttribute("value") == "hardcoded")
|
||||
}
|
||||
|
||||
@ResolveDrivers
|
||||
fun textAreaMutableValueShouldGetOverridden(driver: WebDriver) {
|
||||
driver.openTestPage("textAreaMutableValueShouldGetOverridden")
|
||||
driver.waitTextToBe(value = "InitialValue")
|
||||
|
||||
val controlledTextArea = driver.findElement(By.id("textArea"))
|
||||
controlledTextArea.sendKeys("ABC")
|
||||
|
||||
driver.waitTextToBe(value = "OVERRIDDEN VALUE")
|
||||
check(controlledTextArea.getAttribute("value") == "OVERRIDDEN VALUE")
|
||||
}
|
||||
|
||||
@ResolveDrivers
|
||||
fun textAreaMutableValueShouldChange(driver: WebDriver) {
|
||||
driver.openTestPage("textAreaMutableValueShouldChange")
|
||||
driver.waitTextToBe(value = "InitialValue")
|
||||
|
||||
val controlledTextArea = driver.findElement(By.id("textArea"))
|
||||
|
||||
controlledTextArea.sendKeys("A")
|
||||
driver.waitTextToBe(value = "InitialValueA")
|
||||
|
||||
controlledTextArea.sendKeys("B")
|
||||
driver.waitTextToBe(value = "InitialValueAB")
|
||||
|
||||
controlledTextArea.sendKeys("C")
|
||||
driver.waitTextToBe(value = "InitialValueABC")
|
||||
|
||||
check(controlledTextArea.getAttribute("value") == "InitialValueABC")
|
||||
}
|
||||
|
||||
@ResolveDrivers
|
||||
fun checkBoxHardcodedNeverChanges(driver: WebDriver) {
|
||||
driver.openTestPage("checkBoxHardcodedNeverChanges")
|
||||
driver.waitTextToBe(value = "false")
|
||||
|
||||
val checkbox = driver.findElement(By.id("checkbox"))
|
||||
check(!checkbox.isSelected)
|
||||
|
||||
checkbox.click()
|
||||
|
||||
driver.waitTextToBe(value = "true") // input received but ignored
|
||||
check(!checkbox.isSelected)
|
||||
}
|
||||
|
||||
@ResolveDrivers
|
||||
fun checkBoxMutableValueChanges(driver: WebDriver) {
|
||||
driver.openTestPage("checkBoxMutableValueChanges")
|
||||
driver.waitTextToBe(value = "false")
|
||||
|
||||
val checkbox = driver.findElement(By.id("checkbox"))
|
||||
check(!checkbox.isSelected)
|
||||
|
||||
checkbox.click()
|
||||
|
||||
driver.waitTextToBe(value = "true")
|
||||
check(checkbox.isSelected)
|
||||
}
|
||||
|
||||
@ResolveDrivers
|
||||
fun checkBoxDefaultCheckedChangesDoesntAffectState(driver: WebDriver) {
|
||||
driver.openTestPage("checkBoxDefaultCheckedChangesDoesntAffectState")
|
||||
driver.waitTextToBe(value = "true")
|
||||
|
||||
val mainCheckbox = driver.findElement(By.id("checkboxMain"))
|
||||
val mirrorCheckbox = driver.findElement(By.id("checkboxMirror"))
|
||||
|
||||
check(mainCheckbox.isSelected)
|
||||
check(mirrorCheckbox.isSelected)
|
||||
|
||||
mirrorCheckbox.click()
|
||||
driver.waitTextToBe(value = "true")
|
||||
check(!mirrorCheckbox.isSelected)
|
||||
check(mainCheckbox.isSelected)
|
||||
|
||||
mainCheckbox.click()
|
||||
driver.waitTextToBe(value = "false")
|
||||
check(!mainCheckbox.isSelected)
|
||||
check(!mirrorCheckbox.isSelected)
|
||||
|
||||
mainCheckbox.click()
|
||||
driver.waitTextToBe(value = "true")
|
||||
check(mainCheckbox.isSelected)
|
||||
check(!mirrorCheckbox.isSelected)
|
||||
}
|
||||
|
||||
@ResolveDrivers
|
||||
fun radioHardcodedNeverChanges(driver: WebDriver) {
|
||||
driver.openTestPage("radioHardcodedNeverChanges")
|
||||
|
||||
val radio1 = driver.findElement(By.id("radio1"))
|
||||
val radio2 = driver.findElement(By.id("radio2"))
|
||||
|
||||
check(radio1.isSelected)
|
||||
check(!radio2.isSelected)
|
||||
|
||||
check(radio1.getAttribute("name") == radio2.getAttribute("name"))
|
||||
check(radio1.getAttribute("name") == "group1")
|
||||
check(radio2.getAttribute("name") == "group1")
|
||||
|
||||
radio2.click()
|
||||
|
||||
check(radio1.isSelected)
|
||||
check(!radio2.isSelected)
|
||||
}
|
||||
|
||||
@ResolveDrivers
|
||||
fun radioMutableCheckedChanges(driver: WebDriver) {
|
||||
driver.openTestPage("radioMutableCheckedChanges")
|
||||
driver.waitTextToBe(value = "Checked - 0")
|
||||
|
||||
val radio1 = driver.findElement(By.id("radio1"))
|
||||
val radio2 = driver.findElement(By.id("radio2"))
|
||||
|
||||
check(!radio1.isSelected)
|
||||
check(!radio2.isSelected)
|
||||
|
||||
radio2.click()
|
||||
driver.waitTextToBe(value = "Checked - 2")
|
||||
|
||||
check(!radio1.isSelected)
|
||||
check(radio2.isSelected)
|
||||
|
||||
radio1.click()
|
||||
driver.waitTextToBe(value = "Checked - 1")
|
||||
|
||||
check(radio1.isSelected)
|
||||
check(!radio2.isSelected)
|
||||
}
|
||||
|
||||
@ResolveDrivers
|
||||
fun numberHardcodedNeverChanges(driver: WebDriver) {
|
||||
driver.openTestPage("numberHardcodedNeverChanges")
|
||||
driver.waitTextToBe(value = "None")
|
||||
|
||||
val numberInput = driver.findElement(By.id("numberInput"))
|
||||
|
||||
check(numberInput.getAttribute("value") == "5")
|
||||
|
||||
numberInput.sendKeys("1")
|
||||
driver.waitTextToBe(value = "51")
|
||||
|
||||
check(numberInput.getAttribute("value") == "5")
|
||||
}
|
||||
|
||||
@ResolveDrivers
|
||||
fun numberMutableChanges(driver: WebDriver) {
|
||||
driver.openTestPage("numberMutableChanges")
|
||||
driver.waitTextToBe(value = "5")
|
||||
|
||||
val numberInput = driver.findElement(By.id("numberInput"))
|
||||
|
||||
check(numberInput.getAttribute("value") == "5")
|
||||
|
||||
numberInput.sendKeys("1")
|
||||
driver.waitTextToBe(value = "51")
|
||||
|
||||
check(numberInput.getAttribute("value") == "51")
|
||||
}
|
||||
|
||||
@ResolveDrivers
|
||||
fun rangeHardcodedNeverChanges(driver: WebDriver) {
|
||||
driver.openTestPage("rangeHardcodedNeverChanges")
|
||||
driver.waitTextToBe(value = "None")
|
||||
|
||||
val numberInput = driver.findElement(By.id("rangeInput"))
|
||||
|
||||
check(numberInput.getAttribute("value") == "21")
|
||||
|
||||
numberInput.sendKeys(Keys.ARROW_RIGHT)
|
||||
driver.waitTextToBe(value = "22")
|
||||
check(numberInput.getAttribute("value") == "21")
|
||||
|
||||
numberInput.sendKeys(Keys.ARROW_RIGHT)
|
||||
driver.waitTextToBe(value = "22")
|
||||
check(numberInput.getAttribute("value") == "21")
|
||||
}
|
||||
|
||||
@ResolveDrivers
|
||||
fun rangeMutableChanges(driver: WebDriver) {
|
||||
driver.openTestPage("rangeMutableChanges")
|
||||
driver.waitTextToBe(value = "10")
|
||||
|
||||
val numberInput = driver.findElement(By.id("rangeInput"))
|
||||
|
||||
check(numberInput.getAttribute("value") == "10")
|
||||
|
||||
numberInput.sendKeys(Keys.ARROW_RIGHT)
|
||||
driver.waitTextToBe(value = "11")
|
||||
check(numberInput.getAttribute("value") == "11")
|
||||
|
||||
numberInput.sendKeys(Keys.ARROW_RIGHT)
|
||||
driver.waitTextToBe(value = "12")
|
||||
check(numberInput.getAttribute("value") == "12")
|
||||
}
|
||||
|
||||
@ResolveDrivers
|
||||
fun emailHardcodedNeverChanges(driver: WebDriver) {
|
||||
driver.openTestPage("emailHardcodedNeverChanges")
|
||||
driver.waitTextToBe(value = "None")
|
||||
|
||||
val emailInput = driver.findElement(By.id("emailInput"))
|
||||
check(emailInput.getAttribute("value") == "a@a.abc")
|
||||
|
||||
emailInput.sendKeys("@")
|
||||
driver.waitTextToBe(value = "a@a.abc@")
|
||||
|
||||
check(emailInput.getAttribute("value") == "a@a.abc")
|
||||
}
|
||||
|
||||
@ResolveDrivers
|
||||
fun emailMutableChanges(driver: WebDriver) {
|
||||
driver.openTestPage("emailMutableChanges")
|
||||
driver.waitTextToBe(value = "")
|
||||
|
||||
val emailInput = driver.findElement(By.id("emailInput"))
|
||||
check(emailInput.getAttribute("value") == "")
|
||||
|
||||
emailInput.sendKeys("a")
|
||||
driver.waitTextToBe(value = "a")
|
||||
|
||||
check(emailInput.getAttribute("value") == "a")
|
||||
}
|
||||
|
||||
@ResolveDrivers
|
||||
fun passwordHardcodedNeverChanges(driver: WebDriver) {
|
||||
driver.openTestPage("passwordHardcodedNeverChanges")
|
||||
driver.waitTextToBe(value = "None")
|
||||
|
||||
val passwordInput = driver.findElement(By.id("passwordInput"))
|
||||
check(passwordInput.getAttribute("value") == "123456")
|
||||
|
||||
passwordInput.sendKeys("a")
|
||||
driver.waitTextToBe(value = "123456a")
|
||||
|
||||
check(passwordInput.getAttribute("value") == "123456")
|
||||
}
|
||||
|
||||
@ResolveDrivers
|
||||
fun passwordMutableChanges(driver: WebDriver) {
|
||||
driver.openTestPage("passwordMutableChanges")
|
||||
driver.waitTextToBe(value = "")
|
||||
|
||||
val passwordInput = driver.findElement(By.id("passwordInput"))
|
||||
check(passwordInput.getAttribute("value") == "")
|
||||
|
||||
passwordInput.sendKeys("a")
|
||||
driver.waitTextToBe(value = "a")
|
||||
|
||||
check(passwordInput.getAttribute("value") == "a")
|
||||
}
|
||||
|
||||
@ResolveDrivers
|
||||
fun searchHardcodedNeverChanges(driver: WebDriver) {
|
||||
driver.openTestPage("searchHardcodedNeverChanges")
|
||||
driver.waitTextToBe(value = "None")
|
||||
|
||||
val searchInput = driver.findElement(By.id("searchInput"))
|
||||
check(searchInput.getAttribute("value") == "hardcoded")
|
||||
|
||||
searchInput.sendKeys("a")
|
||||
driver.waitTextToBe(value = "hardcodeda")
|
||||
|
||||
check(searchInput.getAttribute("value") == "hardcoded")
|
||||
}
|
||||
|
||||
@ResolveDrivers
|
||||
fun searchMutableChanges(driver: WebDriver) {
|
||||
driver.openTestPage("searchMutableChanges")
|
||||
driver.waitTextToBe(value = "")
|
||||
|
||||
val searchInput = driver.findElement(By.id("searchInput"))
|
||||
check(searchInput.getAttribute("value") == "")
|
||||
|
||||
searchInput.sendKeys("a")
|
||||
driver.waitTextToBe(value = "a")
|
||||
|
||||
check(searchInput.getAttribute("value") == "a")
|
||||
}
|
||||
|
||||
@ResolveDrivers
|
||||
fun telHardcodedNeverChanges(driver: WebDriver) {
|
||||
driver.openTestPage("telHardcodedNeverChanges")
|
||||
driver.waitTextToBe(value = "None")
|
||||
|
||||
val telInput = driver.findElement(By.id("telInput"))
|
||||
check(telInput.getAttribute("value") == "123456")
|
||||
|
||||
telInput.sendKeys("7")
|
||||
driver.waitTextToBe(value = "1234567")
|
||||
|
||||
check(telInput.getAttribute("value") == "123456")
|
||||
}
|
||||
|
||||
@ResolveDrivers
|
||||
fun telMutableChanges(driver: WebDriver) {
|
||||
driver.openTestPage("telMutableChanges")
|
||||
driver.waitTextToBe(value = "")
|
||||
|
||||
val telInput = driver.findElement(By.id("telInput"))
|
||||
check(telInput.getAttribute("value") == "")
|
||||
|
||||
telInput.sendKeys("1")
|
||||
driver.waitTextToBe(value = "1")
|
||||
|
||||
check(telInput.getAttribute("value") == "1")
|
||||
}
|
||||
|
||||
@ResolveDrivers
|
||||
fun urlHardcodedNeverChanges(driver: WebDriver) {
|
||||
driver.openTestPage("urlHardcodedNeverChanges")
|
||||
driver.waitTextToBe(value = "None")
|
||||
|
||||
val urlInput = driver.findElement(By.id("urlInput"))
|
||||
check(urlInput.getAttribute("value") == "www.site.com")
|
||||
|
||||
urlInput.sendKeys("a")
|
||||
driver.waitTextToBe(value = "www.site.coma")
|
||||
|
||||
check(urlInput.getAttribute("value") == "www.site.com")
|
||||
}
|
||||
|
||||
@ResolveDrivers
|
||||
fun urlMutableChanges(driver: WebDriver) {
|
||||
driver.openTestPage("urlMutableChanges")
|
||||
driver.waitTextToBe(value = "")
|
||||
|
||||
val urlInput = driver.findElement(By.id("urlInput"))
|
||||
check(urlInput.getAttribute("value") == "")
|
||||
|
||||
urlInput.sendKeys("w")
|
||||
driver.waitTextToBe(value = "w")
|
||||
|
||||
check(urlInput.getAttribute("value") == "w")
|
||||
}
|
||||
|
||||
@ResolveDrivers
|
||||
fun hardcodedDateInputNeverChanges(driver: WebDriver) {
|
||||
driver.openTestPage("hardcodedDateInputNeverChanges")
|
||||
driver.waitTextToBe(value = "None")
|
||||
|
||||
val dateInput = driver.findElement(By.id("dateInput"))
|
||||
check(dateInput.getAttribute("value") == "")
|
||||
|
||||
driver.sendKeysForDateInput(dateInput, 2021, 10, 22)
|
||||
|
||||
driver.waitTextToBe(value = "onInput Caught")
|
||||
check(dateInput.getAttribute("value") == "")
|
||||
}
|
||||
|
||||
@ResolveDrivers
|
||||
fun mutableDateInputChanges(driver: WebDriver) {
|
||||
// We skip chrome, since for some reason `sendKeys` doesn't work as expected when used for Controlled Input in Chrome
|
||||
Assumptions.assumeTrue(
|
||||
driver !is ChromeDriver,
|
||||
"chrome driver doesn't work properly when using sendKeys on Controlled Input"
|
||||
)
|
||||
|
||||
driver.openTestPage("mutableDateInputChanges")
|
||||
driver.waitTextToBe(value = "")
|
||||
|
||||
val dateInput = driver.findElement(By.id("dateInput"))
|
||||
check(dateInput.getAttribute("value") == "")
|
||||
|
||||
driver.sendKeysForDateInput(dateInput, 2021, 10, 22)
|
||||
|
||||
driver.waitTextToBe(value = "2021-10-22")
|
||||
check(dateInput.getAttribute("value") == "2021-10-22")
|
||||
}
|
||||
|
||||
@ResolveDrivers
|
||||
fun hardcodedTimeNeverChanges(driver: WebDriver) {
|
||||
driver.openTestPage("hardcodedTimeNeverChanges")
|
||||
driver.waitTextToBe(value = "None")
|
||||
|
||||
val timeInput = driver.findElement(By.id("time"))
|
||||
check(timeInput.getAttribute("value") == "14:00")
|
||||
|
||||
timeInput.sendKeys("18:31")
|
||||
|
||||
driver.waitTextToBe(value = "onInput Caught")
|
||||
check(timeInput.getAttribute("value") == "14:00")
|
||||
}
|
||||
|
||||
@ResolveDrivers
|
||||
fun mutableTimeChanges(driver: WebDriver) {
|
||||
// We skip chrome, since for some reason `sendKeys` doesn't work as expected when used for Controlled Input in Chrome
|
||||
Assumptions.assumeTrue(
|
||||
driver !is ChromeDriver,
|
||||
"chrome driver doesn't work properly when using sendKeys on Controlled Input"
|
||||
)
|
||||
|
||||
driver.openTestPage("mutableTimeChanges")
|
||||
driver.waitTextToBe(value = "")
|
||||
|
||||
val timeInput = driver.findElement(By.id("time"))
|
||||
check(timeInput.getAttribute("value") == "")
|
||||
|
||||
timeInput.sendKeys("18:31")
|
||||
|
||||
driver.waitTextToBe(value = "18:31")
|
||||
check(timeInput.getAttribute("value") == "18:31")
|
||||
}
|
||||
|
||||
@ResolveDrivers
|
||||
fun timeInputSendKeysOnChromeFailingTest(driver: WebDriver) {
|
||||
Assumptions.assumeTrue(
|
||||
driver is ChromeDriver,
|
||||
"this a `failing test for Chrome only` to catch when issue with sendKeys is resolved"
|
||||
)
|
||||
driver.openTestPage("mutableTimeChanges")
|
||||
driver.waitTextToBe(value = "")
|
||||
|
||||
val timeInput = driver.findElement(By.id("time"))
|
||||
|
||||
timeInput.sendKeys("18:31")
|
||||
driver.waitTextToBe(value = "18:03") // it should be 18:31, but this is a failing test, so wrong value is expected
|
||||
}
|
||||
}
|
||||
@@ -25,11 +25,12 @@ class InputsTests : BaseIntegrationTests() {
|
||||
@ResolveDrivers
|
||||
fun `text input gets printed`(driver: WebDriver) {
|
||||
driver.openTestPage("textInputGetsPrinted")
|
||||
driver.waitTextToBe(textId = "txt", value = "Initial-")
|
||||
|
||||
val input = driver.findElement(By.id("input"))
|
||||
input.sendKeys("Hello World!")
|
||||
|
||||
driver.waitTextToBe(textId = "txt", value = "Hello World!")
|
||||
driver.waitTextToBe(textId = "txt", value = "Initial-Hello World!")
|
||||
}
|
||||
|
||||
@ResolveDrivers
|
||||
@@ -98,17 +99,19 @@ class InputsTests : BaseIntegrationTests() {
|
||||
driver.waitTextToBe(value = "15:00")
|
||||
}
|
||||
|
||||
// @_root_ide_package_.org.jetbrains.compose.web.tests.integration.common.ResolveDrivers
|
||||
// fun `date input updates the text`() {
|
||||
// openTestPage("dateInputChangesText")
|
||||
//
|
||||
// waitTextToBe(value = "")
|
||||
//
|
||||
// val timeInput = driver.findElement(By.id("date"))
|
||||
//
|
||||
// timeInput.sendKeys("12102021")
|
||||
// waitTextToBe(value = "2021-10-12")
|
||||
// }
|
||||
@ResolveDrivers
|
||||
fun `date input updates the text`(driver: WebDriver) {
|
||||
driver.openTestPage("dateInputChangesText")
|
||||
|
||||
driver.waitTextToBe(value = "")
|
||||
|
||||
val dateInput = driver.findElement(By.id("date"))
|
||||
|
||||
// we use the same value of month and day here to avoid a need for a more complex formatting
|
||||
driver.sendKeysForDateInput(dateInput, 2021, 10, 10)
|
||||
|
||||
driver.waitTextToBe(value = "2021-10-10")
|
||||
}
|
||||
|
||||
// @_root_ide_package_.org.jetbrains.compose.web.tests.integration.common.ResolveDrivers
|
||||
// fun `dateTimeLocal input updates the text`() { // WARNING: It's not supported in Firefox
|
||||
|
||||
@@ -0,0 +1,208 @@
|
||||
/*
|
||||
* Copyright 2020-2021 JetBrains s.r.o. and respective authors and developers.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.compose.web.tests.integration
|
||||
|
||||
import org.jetbrains.compose.web.tests.integration.common.BaseIntegrationTests
|
||||
import org.jetbrains.compose.web.tests.integration.common.ResolveDrivers
|
||||
import org.jetbrains.compose.web.tests.integration.common.openTestPage
|
||||
import org.jetbrains.compose.web.tests.integration.common.waitTextToBe
|
||||
import org.openqa.selenium.By
|
||||
import org.openqa.selenium.Keys
|
||||
import org.openqa.selenium.WebDriver
|
||||
|
||||
class UncontrolledInputsTests : BaseIntegrationTests() {
|
||||
|
||||
@ResolveDrivers
|
||||
fun textInputDefaultValueRemainsTheSameButValueCanBeChanged(driver: WebDriver) {
|
||||
driver.openTestPage("textInputDefaultValueRemainsTheSameButValueCanBeChanged")
|
||||
|
||||
val input = driver.findElement(By.id("textInput"))
|
||||
check(input.getAttribute("value") == "defaultInputValue")
|
||||
|
||||
input.sendKeys("-TypedText")
|
||||
|
||||
val inputHtml = driver.outerHtmlOfElementWithId("textInput")
|
||||
|
||||
check(inputHtml.contains("value=\"defaultInputValue\""))
|
||||
check(input.getAttribute("value") == "defaultInputValue-TypedText") // this checks the `value` property of the input
|
||||
check(input.getAttribute("data-input-value") == "defaultInputValue-TypedText")
|
||||
}
|
||||
|
||||
@ResolveDrivers
|
||||
fun textAreaDefaultValueRemainsTheSameButValueCanBeChanged(driver: WebDriver) {
|
||||
driver.openTestPage("textAreaDefaultValueRemainsTheSameButValueCanBeChanged")
|
||||
|
||||
val textArea = driver.findElement(By.id("textArea"))
|
||||
check(textArea.getAttribute("value") == "defaultTextAreaValue")
|
||||
|
||||
textArea.sendKeys("-TypedText")
|
||||
|
||||
val innerTextOfTextArea = driver.outerHtmlOfElementWithId("textArea")
|
||||
|
||||
check(innerTextOfTextArea.contains(">defaultTextAreaValue</")) // inner text keeps default value
|
||||
check(textArea.getAttribute("value") == "defaultTextAreaValue-TypedText") // this checks the `value` property of the textarea
|
||||
check(textArea.getAttribute("data-text-area-value") == "defaultTextAreaValue-TypedText")
|
||||
}
|
||||
|
||||
@ResolveDrivers
|
||||
fun checkBoxDefaultCheckedRemainsTheSameButCheckedCanBeChanged(driver: WebDriver) {
|
||||
driver.openTestPage("checkBoxDefaultCheckedRemainsTheSameButCheckedCanBeChanged")
|
||||
|
||||
val checkbox = driver.findElement(By.id("checkbox"))
|
||||
|
||||
val innerTextOfCheckbox1 = driver.outerHtmlOfElementWithId("checkbox")
|
||||
check(innerTextOfCheckbox1.contains("checked"))
|
||||
check(checkbox.getAttribute("value") == "checkbox-value")
|
||||
check(checkbox.getAttribute("data-checkbox") == "true")
|
||||
check(checkbox.isSelected)
|
||||
|
||||
checkbox.click()
|
||||
|
||||
val innerTextOfCheckbox2 = driver.outerHtmlOfElementWithId("checkbox")
|
||||
check(innerTextOfCheckbox2.contains("checked"))
|
||||
check(checkbox.getAttribute("value") == "checkbox-value")
|
||||
check(checkbox.getAttribute("data-checkbox") == "false")
|
||||
check(!checkbox.isSelected)
|
||||
}
|
||||
|
||||
@ResolveDrivers
|
||||
fun radioDefaultCheckedRemainsTheSameButCheckedCanBeChanged(driver: WebDriver) {
|
||||
driver.openTestPage("radioDefaultCheckedRemainsTheSameButCheckedCanBeChanged")
|
||||
|
||||
val radio1 = driver.findElement(By.id("radio1"))
|
||||
val radio2 = driver.findElement(By.id("radio2"))
|
||||
|
||||
check(radio1.isSelected)
|
||||
check(!radio2.isSelected)
|
||||
|
||||
check(driver.outerHtmlOfElementWithId("radio1").contains("checked"))
|
||||
check(!driver.outerHtmlOfElementWithId("radio2").contains("checked"))
|
||||
|
||||
radio2.click()
|
||||
|
||||
check(!radio1.isSelected)
|
||||
check(radio2.isSelected)
|
||||
|
||||
check(driver.outerHtmlOfElementWithId("radio1").contains("checked"))
|
||||
check(!driver.outerHtmlOfElementWithId("radio2").contains("checked"))
|
||||
}
|
||||
|
||||
@ResolveDrivers
|
||||
fun numberDefaultValueRemainsTheSameButValueCanBeChanged(driver: WebDriver) {
|
||||
driver.openTestPage("numberDefaultValueRemainsTheSameButValueCanBeChanged")
|
||||
driver.waitTextToBe(value = "Value = None")
|
||||
|
||||
val numberInput = driver.findElement(By.id("numberInput"))
|
||||
check(numberInput.getAttribute("value") == "11")
|
||||
|
||||
numberInput.sendKeys("5")
|
||||
driver.waitTextToBe(value = "Value = 511")
|
||||
|
||||
check(numberInput.getAttribute("value") == "511")
|
||||
check(driver.outerHtmlOfElementWithId("numberInput").contains("value=\"11\""))
|
||||
}
|
||||
|
||||
@ResolveDrivers
|
||||
fun rangeDefaultValueRemainsTheSameButValueCanBeChanged(driver: WebDriver) {
|
||||
driver.openTestPage("rangeDefaultValueRemainsTheSameButValueCanBeChanged")
|
||||
driver.waitTextToBe(value = "Value = None")
|
||||
|
||||
val numberInput = driver.findElement(By.id("rangeInput"))
|
||||
check(numberInput.getAttribute("value") == "7")
|
||||
|
||||
numberInput.sendKeys(Keys.ARROW_RIGHT)
|
||||
driver.waitTextToBe(value = "Value = 8")
|
||||
|
||||
numberInput.sendKeys(Keys.ARROW_RIGHT)
|
||||
driver.waitTextToBe(value = "Value = 9")
|
||||
|
||||
check(numberInput.getAttribute("value") == "9")
|
||||
check(driver.outerHtmlOfElementWithId("rangeInput").contains("value=\"7\""))
|
||||
}
|
||||
|
||||
@ResolveDrivers
|
||||
fun emailDefaultValueRemainsTheSameButValueCanBeChanged(driver: WebDriver) {
|
||||
driver.openTestPage("emailDefaultValueRemainsTheSameButValueCanBeChanged")
|
||||
driver.waitTextToBe(value = "Value = None")
|
||||
|
||||
val emailInput = driver.findElement(By.id("emailInput"))
|
||||
check(emailInput.getAttribute("value") == "a@a.abc")
|
||||
|
||||
emailInput.clear()
|
||||
emailInput.sendKeys("u@u.com")
|
||||
|
||||
driver.waitTextToBe(value = "Value = u@u.com")
|
||||
check(emailInput.getAttribute("value") == "u@u.com")
|
||||
|
||||
check(driver.outerHtmlOfElementWithId("emailInput").contains("value=\"a@a.abc\""))
|
||||
}
|
||||
|
||||
@ResolveDrivers
|
||||
fun passwordDefaultValueRemainsTheSameButValueCanBeChanged(driver: WebDriver) {
|
||||
driver.openTestPage("passwordDefaultValueRemainsTheSameButValueCanBeChanged")
|
||||
driver.waitTextToBe(value = "Value = None")
|
||||
|
||||
val passwordInput = driver.findElement(By.id("passwordInput"))
|
||||
check(passwordInput.getAttribute("value") == "1111")
|
||||
|
||||
passwordInput.clear()
|
||||
passwordInput.sendKeys("a")
|
||||
|
||||
driver.waitTextToBe(value = "Value = a")
|
||||
check(passwordInput.getAttribute("value") == "a")
|
||||
|
||||
check(driver.outerHtmlOfElementWithId("passwordInput").contains("value=\"1111\""))
|
||||
}
|
||||
|
||||
@ResolveDrivers
|
||||
fun searchDefaultValueRemainsTheSameButValueCanBeChanged(driver: WebDriver) {
|
||||
driver.openTestPage("searchDefaultValueRemainsTheSameButValueCanBeChanged")
|
||||
driver.waitTextToBe(value = "Value = None")
|
||||
|
||||
val searchInput = driver.findElement(By.id("searchInput"))
|
||||
check(searchInput.getAttribute("value") == "kotlin")
|
||||
|
||||
searchInput.clear()
|
||||
searchInput.sendKeys("j")
|
||||
driver.waitTextToBe(value = "Value = j")
|
||||
|
||||
check(searchInput.getAttribute("value") == "j")
|
||||
|
||||
check(driver.outerHtmlOfElementWithId("searchInput").contains("value=\"kotlin\""))
|
||||
}
|
||||
|
||||
@ResolveDrivers
|
||||
fun telDefaultValueRemainsTheSameButValueCanBeChanged(driver: WebDriver) {
|
||||
driver.openTestPage("telDefaultValueRemainsTheSameButValueCanBeChanged")
|
||||
driver.waitTextToBe(value = "None")
|
||||
|
||||
val telInput = driver.findElement(By.id("telInput"))
|
||||
check(telInput.getAttribute("value") == "123123")
|
||||
|
||||
telInput.clear()
|
||||
telInput.sendKeys("987654321")
|
||||
|
||||
driver.waitTextToBe(value = "987654321")
|
||||
check(telInput.getAttribute("value") == "987654321")
|
||||
check(driver.outerHtmlOfElementWithId("telInput").contains("value=\"123123\""))
|
||||
}
|
||||
|
||||
@ResolveDrivers
|
||||
fun urlDefaultValueRemainsTheSameButValueCanBeChanged(driver: WebDriver) {
|
||||
driver.openTestPage("urlDefaultValueRemainsTheSameButValueCanBeChanged")
|
||||
driver.waitTextToBe(value = "None")
|
||||
|
||||
val urlInput = driver.findElement(By.id("urlInput"))
|
||||
check(urlInput.getAttribute("value") == "www.site.com")
|
||||
|
||||
urlInput.clear()
|
||||
urlInput.sendKeys("google.com")
|
||||
|
||||
driver.waitTextToBe(value = "google.com")
|
||||
check(urlInput.getAttribute("value") == "google.com")
|
||||
check(driver.outerHtmlOfElementWithId("urlInput").contains("value=\"www.site.com\""))
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,9 @@ import org.junit.jupiter.api.extension.ExtendWith
|
||||
import org.junit.jupiter.params.ParameterizedTest
|
||||
import org.junit.jupiter.params.provider.MethodSource
|
||||
import org.openqa.selenium.By
|
||||
import org.openqa.selenium.JavascriptExecutor
|
||||
import org.openqa.selenium.WebDriver
|
||||
import org.openqa.selenium.WebElement
|
||||
import org.openqa.selenium.chrome.ChromeDriver
|
||||
import org.openqa.selenium.chrome.ChromeOptions
|
||||
import org.openqa.selenium.firefox.FirefoxDriver
|
||||
@@ -83,4 +85,23 @@ abstract class BaseIntegrationTests() {
|
||||
@JvmStatic
|
||||
fun resolveDrivers() = Drivers.activatedDrivers
|
||||
}
|
||||
|
||||
fun WebDriver.outerHtmlOfElementWithId(id: String): String {
|
||||
val script = """
|
||||
var callback = arguments[arguments.length - 1];
|
||||
callback(document.getElementById("$id").outerHTML);
|
||||
""".trimIndent()
|
||||
|
||||
return (this as JavascriptExecutor).executeAsyncScript(script).toString()
|
||||
}
|
||||
|
||||
fun WebDriver.sendKeysForDateInput(input: WebElement, year: Int, month: Int, day: Int) {
|
||||
val keys = when (this) {
|
||||
is ChromeDriver -> "${day}${month}${year}"
|
||||
is FirefoxDriver -> "${year}-${month}-${day}"
|
||||
else -> ""
|
||||
}
|
||||
|
||||
input.sendKeys(keys)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user