Browse Source
* web: Add more tests for the event handlers * web: Inputs refactoring (wip) * web: Add `Options` for `addEventListener` * web: Add basic methods and properties to the SyntheticInputEvent to align it with `org.w3c.dom.events.Event` * web: Add a test for `capture = true` Event Listener Option * web: Update PR to make contain only relevant changes + add specific functions for Inputs * web: Update PR to align with master after rebase Co-authored-by: Oleksandr Karpovich <oleksandr.karpovich@jetbrains.com>pull/822/head
Oleksandr Karpovich
3 years ago
committed by
GitHub
24 changed files with 1367 additions and 150 deletions
@ -0,0 +1,93 @@ |
|||||||
|
/* |
||||||
|
* 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.attributes |
||||||
|
|
||||||
|
import org.jetbrains.compose.web.attributes.* |
||||||
|
import org.jetbrains.compose.web.events.GenericWrappedEvent |
||||||
|
import org.jetbrains.compose.web.events.WrappedCheckBoxInputEvent |
||||||
|
import org.jetbrains.compose.web.events.WrappedRadioInputEvent |
||||||
|
import org.jetbrains.compose.web.events.WrappedTextInputEvent |
||||||
|
import org.w3c.dom.HTMLElement |
||||||
|
import org.w3c.dom.HTMLInputElement |
||||||
|
import org.w3c.dom.events.Event |
||||||
|
import org.w3c.dom.events.EventTarget |
||||||
|
|
||||||
|
class SyntheticInputEvent<ValueType, Element : HTMLElement>( |
||||||
|
val value: ValueType, |
||||||
|
val target: Element, |
||||||
|
val nativeEvent: Event |
||||||
|
) { |
||||||
|
|
||||||
|
val bubbles: Boolean = nativeEvent.bubbles |
||||||
|
val cancelable: Boolean = nativeEvent.cancelable |
||||||
|
val composed: Boolean = nativeEvent.composed |
||||||
|
val currentTarget: HTMLElement? = nativeEvent.currentTarget.unsafeCast<HTMLInputElement?>() |
||||||
|
val eventPhase: Short = nativeEvent.eventPhase |
||||||
|
val defaultPrevented: Boolean = nativeEvent.defaultPrevented |
||||||
|
val timestamp: Number = nativeEvent.timeStamp |
||||||
|
val type: String = nativeEvent.type |
||||||
|
val isTrusted: Boolean = nativeEvent.isTrusted |
||||||
|
|
||||||
|
fun preventDefault(): Unit = nativeEvent.preventDefault() |
||||||
|
fun stopPropagation(): Unit = nativeEvent.stopPropagation() |
||||||
|
fun stopImmediatePropagation(): Unit = nativeEvent.stopImmediatePropagation() |
||||||
|
fun composedPath(): Array<EventTarget> = nativeEvent.composedPath() |
||||||
|
} |
||||||
|
|
||||||
|
class InputAttrsBuilder<T>(val inputType: InputType<T>) : AttrsBuilder<HTMLInputElement>() { |
||||||
|
|
||||||
|
fun onInput(options: Options = Options.DEFAULT, listener: (SyntheticInputEvent<T, HTMLInputElement>) -> Unit) { |
||||||
|
addEventListener(INPUT, options) { |
||||||
|
val value = inputType.inputValue(it.nativeEvent) |
||||||
|
listener(SyntheticInputEvent(value, it.nativeEvent.target as HTMLInputElement, it.nativeEvent)) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Deprecated( |
||||||
|
message = "It's not reliable as it can be applied to any input type.", |
||||||
|
replaceWith = ReplaceWith("onInput(options, listener)"), |
||||||
|
level = DeprecationLevel.WARNING |
||||||
|
) |
||||||
|
fun onTextInput(options: Options = Options.DEFAULT, listener: (WrappedTextInputEvent) -> Unit) { |
||||||
|
listeners.add(TextInputEventListener(options, listener)) |
||||||
|
} |
||||||
|
|
||||||
|
@Deprecated( |
||||||
|
message = "It's not reliable as it can be applied to any input type.", |
||||||
|
replaceWith = ReplaceWith("onInput(options, listener)"), |
||||||
|
level = DeprecationLevel.WARNING |
||||||
|
) |
||||||
|
fun onCheckboxInput( |
||||||
|
options: Options = Options.DEFAULT, |
||||||
|
listener: (WrappedCheckBoxInputEvent) -> Unit |
||||||
|
) { |
||||||
|
listeners.add(CheckBoxInputEventListener(options, listener)) |
||||||
|
} |
||||||
|
|
||||||
|
@Deprecated( |
||||||
|
message = "It's not reliable as it can be applied to any input type.", |
||||||
|
replaceWith = ReplaceWith("onInput(options, listener)"), |
||||||
|
level = DeprecationLevel.WARNING |
||||||
|
) |
||||||
|
fun onRadioInput( |
||||||
|
options: Options = Options.DEFAULT, |
||||||
|
listener: (WrappedRadioInputEvent) -> Unit |
||||||
|
) { |
||||||
|
listeners.add(RadioInputEventListener(options, listener)) |
||||||
|
} |
||||||
|
|
||||||
|
@Deprecated( |
||||||
|
message = "It's not reliable as it can be applied to any input type.", |
||||||
|
replaceWith = ReplaceWith("onInput(options, listener)"), |
||||||
|
level = DeprecationLevel.WARNING |
||||||
|
) |
||||||
|
fun onRangeInput( |
||||||
|
options: Options = Options.DEFAULT, |
||||||
|
listener: (GenericWrappedEvent<*>) -> Unit |
||||||
|
) { |
||||||
|
listeners.add(WrappedEventListener(INPUT, options, listener)) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,32 @@ |
|||||||
|
/* |
||||||
|
* 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.attributes |
||||||
|
|
||||||
|
import org.jetbrains.compose.web.attributes.* |
||||||
|
import org.jetbrains.compose.web.events.WrappedTextInputEvent |
||||||
|
import org.w3c.dom.HTMLTextAreaElement |
||||||
|
|
||||||
|
class TextAreaAttrsBuilder : AttrsBuilder<HTMLTextAreaElement>() { |
||||||
|
|
||||||
|
fun onInput( |
||||||
|
options: Options = Options.DEFAULT, |
||||||
|
listener: (SyntheticInputEvent<String, HTMLTextAreaElement>) -> Unit |
||||||
|
) { |
||||||
|
addEventListener(INPUT, options) { |
||||||
|
val text = it.nativeEvent.target.asDynamic().value.unsafeCast<String>() |
||||||
|
listener(SyntheticInputEvent(text, it.nativeEvent.target as HTMLTextAreaElement, it.nativeEvent)) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Deprecated( |
||||||
|
message = "It's not reliable as it can be applied to any input type.", |
||||||
|
replaceWith = ReplaceWith("onInput(options, listener)"), |
||||||
|
level = DeprecationLevel.WARNING |
||||||
|
) |
||||||
|
fun onTextInput(options: Options = Options.DEFAULT, listener: (WrappedTextInputEvent) -> Unit) { |
||||||
|
listeners.add(TextInputEventListener(options, listener)) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,192 @@ |
|||||||
|
package org.jetbrains.compose.web.dom |
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable |
||||||
|
import androidx.compose.runtime.NonRestartableComposable |
||||||
|
import androidx.compose.web.attributes.InputAttrsBuilder |
||||||
|
import org.jetbrains.compose.web.attributes.* |
||||||
|
|
||||||
|
private fun InputAttrsBuilder<String>.applyAttrsWithStringValue( |
||||||
|
value: String, |
||||||
|
attrsBuilder: InputAttrsBuilder<String>.() -> Unit |
||||||
|
) { |
||||||
|
if (value.isNotEmpty()) value(value) |
||||||
|
attrsBuilder() |
||||||
|
} |
||||||
|
|
||||||
|
@Composable |
||||||
|
@NonRestartableComposable |
||||||
|
fun CheckboxInput(checked: Boolean = false, attrsBuilder: InputAttrsBuilder<Boolean>.() -> Unit = {}) { |
||||||
|
Input( |
||||||
|
type = InputType.Checkbox, |
||||||
|
attrs = { |
||||||
|
if (checked) checked() |
||||||
|
this.attrsBuilder() |
||||||
|
} |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
@Composable |
||||||
|
@NonRestartableComposable |
||||||
|
fun DateInput(value: String = "", attrsBuilder: InputAttrsBuilder<String>.() -> Unit = {}) { |
||||||
|
Input(type = InputType.Date, attrs = { applyAttrsWithStringValue(value, attrsBuilder) }) |
||||||
|
} |
||||||
|
|
||||||
|
@Composable |
||||||
|
@NonRestartableComposable |
||||||
|
fun DateTimeLocalInput(value: String = "", attrsBuilder: InputAttrsBuilder<String>.() -> Unit = {}) { |
||||||
|
Input(type = InputType.DateTimeLocal, attrs = { applyAttrsWithStringValue(value, attrsBuilder) }) |
||||||
|
} |
||||||
|
|
||||||
|
@Composable |
||||||
|
@NonRestartableComposable |
||||||
|
fun EmailInput(value: String = "", attrsBuilder: InputAttrsBuilder<String>.() -> Unit = {}) { |
||||||
|
Input(type = InputType.Email, attrs = { applyAttrsWithStringValue(value, attrsBuilder) }) |
||||||
|
} |
||||||
|
|
||||||
|
@Composable |
||||||
|
@NonRestartableComposable |
||||||
|
fun FileInput(value: String = "", attrsBuilder: InputAttrsBuilder<String>.() -> Unit = {}) { |
||||||
|
Input(type = InputType.File, attrs = { applyAttrsWithStringValue(value, attrsBuilder) }) |
||||||
|
} |
||||||
|
|
||||||
|
@Composable |
||||||
|
@NonRestartableComposable |
||||||
|
fun HiddenInput(attrsBuilder: InputAttrsBuilder<String>.() -> Unit = {}) { |
||||||
|
Input(type = InputType.Hidden, attrs = attrsBuilder) |
||||||
|
} |
||||||
|
|
||||||
|
@Composable |
||||||
|
@NonRestartableComposable |
||||||
|
fun MonthInput(value: String = "", attrsBuilder: InputAttrsBuilder<String>.() -> Unit = {}) { |
||||||
|
Input(type = InputType.Month, attrs = { applyAttrsWithStringValue(value, attrsBuilder) }) |
||||||
|
} |
||||||
|
|
||||||
|
@Composable |
||||||
|
@NonRestartableComposable |
||||||
|
fun NumberInput( |
||||||
|
value: Number? = null, |
||||||
|
min: Number? = null, |
||||||
|
max: Number? = null, |
||||||
|
attrsBuilder: InputAttrsBuilder<Number?>.() -> Unit = {} |
||||||
|
) { |
||||||
|
Input( |
||||||
|
type = InputType.Number, |
||||||
|
attrs = { |
||||||
|
if (value != null) value(value.toString()) |
||||||
|
if (min != null) min(min.toString()) |
||||||
|
if (max != null) max(max.toString()) |
||||||
|
attrsBuilder() |
||||||
|
} |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
@Composable |
||||||
|
@NonRestartableComposable |
||||||
|
fun PasswordInput(value: String = "", attrsBuilder: InputAttrsBuilder<String>.() -> Unit = {}) { |
||||||
|
Input(type = InputType.Password, attrs = { applyAttrsWithStringValue(value, attrsBuilder) }) |
||||||
|
} |
||||||
|
|
||||||
|
@Composable |
||||||
|
@NonRestartableComposable |
||||||
|
fun RadioInput(checked: Boolean = false, attrsBuilder: InputAttrsBuilder<Boolean>.() -> Unit = {}) { |
||||||
|
Input( |
||||||
|
type = InputType.Radio, |
||||||
|
attrs = { |
||||||
|
if (checked) checked() |
||||||
|
attrsBuilder() |
||||||
|
} |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
@Composable |
||||||
|
@NonRestartableComposable |
||||||
|
fun RangeInput( |
||||||
|
value: Number? = null, |
||||||
|
min: Number? = null, |
||||||
|
max: Number? = null, |
||||||
|
step: Number = 1, |
||||||
|
attrsBuilder: InputAttrsBuilder<Number?>.() -> Unit = {} |
||||||
|
) { |
||||||
|
Input( |
||||||
|
type = InputType.Range, |
||||||
|
attrs = { |
||||||
|
if (value != null) value(value.toString()) |
||||||
|
if (min != null) min(min.toString()) |
||||||
|
if (max != null) max(max.toString()) |
||||||
|
step(step) |
||||||
|
attrsBuilder() |
||||||
|
} |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
@Composable |
||||||
|
@NonRestartableComposable |
||||||
|
fun SearchInput(value: String = "", attrsBuilder: InputAttrsBuilder<String>.() -> Unit = {}) { |
||||||
|
Input(type = InputType.Search, attrs = { applyAttrsWithStringValue(value, attrsBuilder) }) |
||||||
|
} |
||||||
|
|
||||||
|
@Composable |
||||||
|
@NonRestartableComposable |
||||||
|
fun SubmitInput(attrsBuilder: InputAttrsBuilder<Unit>.() -> Unit = {}) { |
||||||
|
Input(type = InputType.Submit, attrs = attrsBuilder) |
||||||
|
} |
||||||
|
|
||||||
|
@Composable |
||||||
|
@NonRestartableComposable |
||||||
|
fun TelInput(value: String = "", attrsBuilder: InputAttrsBuilder<String>.() -> Unit = {}) { |
||||||
|
Input(type = InputType.Tel, attrs = { applyAttrsWithStringValue(value, attrsBuilder) }) |
||||||
|
} |
||||||
|
|
||||||
|
@Composable |
||||||
|
@NonRestartableComposable |
||||||
|
fun TextInput(value: String = "", attrsBuilder: InputAttrsBuilder<String>.() -> Unit = {}) { |
||||||
|
Input(type = InputType.Text, attrs = { applyAttrsWithStringValue(value, attrsBuilder) }) |
||||||
|
} |
||||||
|
|
||||||
|
@Composable |
||||||
|
@NonRestartableComposable |
||||||
|
fun TimeInput(value: String = "", attrsBuilder: InputAttrsBuilder<String>.() -> Unit = {}) { |
||||||
|
Input(type = InputType.Time, attrs = { applyAttrsWithStringValue(value, attrsBuilder) }) |
||||||
|
} |
||||||
|
|
||||||
|
@Composable |
||||||
|
@NonRestartableComposable |
||||||
|
fun UrlInput(value: String = "", attrsBuilder: InputAttrsBuilder<String>.() -> Unit = {}) { |
||||||
|
Input(type = InputType.Url, attrs = { applyAttrsWithStringValue(value, attrsBuilder) }) |
||||||
|
} |
||||||
|
|
||||||
|
@Composable |
||||||
|
@NonRestartableComposable |
||||||
|
fun WeekInput(value: String = "", attrsBuilder: InputAttrsBuilder<String>.() -> Unit = {}) { |
||||||
|
Input(type = InputType.Week, attrs = { applyAttrsWithStringValue(value, attrsBuilder) }) |
||||||
|
} |
||||||
|
|
||||||
|
@Composable |
||||||
|
fun <K> Input( |
||||||
|
type: InputType<K>, |
||||||
|
attrs: InputAttrsBuilder<K>.() -> Unit |
||||||
|
) { |
||||||
|
TagElement( |
||||||
|
elementBuilder = ElementBuilder.Input, |
||||||
|
applyAttrs = { |
||||||
|
val inputAttrsBuilder = InputAttrsBuilder(type) |
||||||
|
inputAttrsBuilder.type(type) |
||||||
|
inputAttrsBuilder.attrs() |
||||||
|
this.copyFrom(inputAttrsBuilder) |
||||||
|
}, |
||||||
|
content = null |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
@Composable |
||||||
|
fun <K> Input(type: InputType<K>) { |
||||||
|
TagElement( |
||||||
|
elementBuilder = ElementBuilder.Input, |
||||||
|
applyAttrs = { |
||||||
|
val inputAttrsBuilder = InputAttrsBuilder(type) |
||||||
|
inputAttrsBuilder.type(type) |
||||||
|
this.copyFrom(inputAttrsBuilder) |
||||||
|
}, |
||||||
|
content = null |
||||||
|
) |
||||||
|
} |
@ -0,0 +1,388 @@ |
|||||||
|
package org.jetbrains.compose.web.core.tests.elements |
||||||
|
|
||||||
|
import org.jetbrains.compose.web.core.tests.runTest |
||||||
|
import org.jetbrains.compose.web.dom.* |
||||||
|
import org.w3c.dom.HTMLInputElement |
||||||
|
import kotlin.test.Test |
||||||
|
import kotlin.test.assertEquals |
||||||
|
|
||||||
|
class InputsGenerateCorrectHtmlTests { |
||||||
|
|
||||||
|
@Test |
||||||
|
fun checkBoxInput() = runTest { |
||||||
|
composition { |
||||||
|
CheckboxInput(checked = true) { |
||||||
|
id("checkboxId") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
val checkboxInput = root.firstChild as HTMLInputElement |
||||||
|
|
||||||
|
assertEquals("checkbox", checkboxInput.getAttribute("type")) |
||||||
|
assertEquals("checkboxId", checkboxInput.getAttribute("id")) |
||||||
|
assertEquals(true, checkboxInput.checked) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun checkBoxInputWithDefaults() = runTest { |
||||||
|
composition { CheckboxInput() } |
||||||
|
|
||||||
|
val checkboxInput = root.firstChild as HTMLInputElement |
||||||
|
|
||||||
|
assertEquals("checkbox", checkboxInput.getAttribute("type")) |
||||||
|
assertEquals(null, checkboxInput.getAttribute("id")) |
||||||
|
assertEquals(false, checkboxInput.checked) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun dateInput() = runTest { |
||||||
|
composition { |
||||||
|
DateInput(value = "2021-10-10") { |
||||||
|
id("dateInputId") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
val dateInput = root.firstChild as HTMLInputElement |
||||||
|
|
||||||
|
assertEquals("date", dateInput.getAttribute("type")) |
||||||
|
assertEquals("dateInputId", dateInput.getAttribute("id")) |
||||||
|
assertEquals("2021-10-10", dateInput.value) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun dateInputWithDefaults() = runTest { |
||||||
|
composition { DateInput() } |
||||||
|
|
||||||
|
val dateInput = root.firstChild as HTMLInputElement |
||||||
|
|
||||||
|
assertEquals("date", dateInput.getAttribute("type")) |
||||||
|
assertEquals(null, dateInput.getAttribute("id")) |
||||||
|
assertEquals("", dateInput.value) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun emailInput() = runTest { |
||||||
|
composition { |
||||||
|
EmailInput(value = "user@mail.com") { |
||||||
|
id("emailInputId") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
val emailInput = root.firstChild as HTMLInputElement |
||||||
|
|
||||||
|
assertEquals("email", emailInput.getAttribute("type")) |
||||||
|
assertEquals("emailInputId", emailInput.getAttribute("id")) |
||||||
|
assertEquals("user@mail.com", emailInput.value) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun emailInputWithDefaults() = runTest { |
||||||
|
composition { EmailInput() } |
||||||
|
|
||||||
|
val emailInput = root.firstChild as HTMLInputElement |
||||||
|
|
||||||
|
assertEquals("email", emailInput.getAttribute("type")) |
||||||
|
assertEquals(null, emailInput.getAttribute("id")) |
||||||
|
assertEquals("", emailInput.value) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun monthInput() = runTest { |
||||||
|
composition { |
||||||
|
MonthInput(value = "2017-06") { |
||||||
|
id("monthInputId") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
val monthInput = root.firstChild as HTMLInputElement |
||||||
|
|
||||||
|
assertEquals("month", monthInput.getAttribute("type")) |
||||||
|
assertEquals("monthInputId", monthInput.getAttribute("id")) |
||||||
|
assertEquals("2017-06", monthInput.value) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun monthInputWithDefaults() = runTest { |
||||||
|
composition { MonthInput() } |
||||||
|
|
||||||
|
val monthInput = root.firstChild as HTMLInputElement |
||||||
|
|
||||||
|
assertEquals("month", monthInput.getAttribute("type")) |
||||||
|
assertEquals(null, monthInput.getAttribute("id")) |
||||||
|
assertEquals("", monthInput.value) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun numberInput() = runTest { |
||||||
|
composition { |
||||||
|
NumberInput(value = 100, min = 10, max = 200) { |
||||||
|
id("numberInputId") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
val numberInput = root.firstChild as HTMLInputElement |
||||||
|
|
||||||
|
assertEquals("number", numberInput.getAttribute("type")) |
||||||
|
assertEquals("numberInputId", numberInput.getAttribute("id")) |
||||||
|
assertEquals("200", numberInput.getAttribute("max")) |
||||||
|
assertEquals("10", numberInput.getAttribute("min")) |
||||||
|
assertEquals(100, numberInput.valueAsNumber.toInt()) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun numberInputWithDefaults() = runTest { |
||||||
|
composition { |
||||||
|
NumberInput { |
||||||
|
id("numberInputId") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
val numberInput = root.firstChild as HTMLInputElement |
||||||
|
|
||||||
|
assertEquals("number", numberInput.getAttribute("type")) |
||||||
|
assertEquals("numberInputId", numberInput.getAttribute("id")) |
||||||
|
assertEquals(null, numberInput.getAttribute("max")) |
||||||
|
assertEquals(null, numberInput.getAttribute("min")) |
||||||
|
assertEquals("", numberInput.value) |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
@Test |
||||||
|
fun passwordInput() = runTest { |
||||||
|
composition { |
||||||
|
PasswordInput(value = "somepassword") { |
||||||
|
id("passwordInputId") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
val passwordInput = root.firstChild as HTMLInputElement |
||||||
|
|
||||||
|
assertEquals("password", passwordInput.getAttribute("type")) |
||||||
|
assertEquals("passwordInputId", passwordInput.getAttribute("id")) |
||||||
|
assertEquals("somepassword", passwordInput.value) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun passwordInputWithDefaults() = runTest { |
||||||
|
composition { PasswordInput() } |
||||||
|
|
||||||
|
val passwordInput = root.firstChild as HTMLInputElement |
||||||
|
|
||||||
|
assertEquals("password", passwordInput.getAttribute("type")) |
||||||
|
assertEquals(null, passwordInput.getAttribute("id")) |
||||||
|
assertEquals("", passwordInput.value) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun radioInput() = runTest { |
||||||
|
composition { |
||||||
|
RadioInput(checked = true) { |
||||||
|
id("radioInputId") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
val radioInput = root.firstChild as HTMLInputElement |
||||||
|
|
||||||
|
assertEquals("radio", radioInput.getAttribute("type")) |
||||||
|
assertEquals("radioInputId", radioInput.getAttribute("id")) |
||||||
|
assertEquals(true, radioInput.checked) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun radioInputWithDefaults() = runTest { |
||||||
|
composition { RadioInput() } |
||||||
|
|
||||||
|
val radioInput = root.firstChild as HTMLInputElement |
||||||
|
|
||||||
|
assertEquals("radio", radioInput.getAttribute("type")) |
||||||
|
assertEquals(null, radioInput.getAttribute("id")) |
||||||
|
assertEquals(false, radioInput.checked) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun rangeInput() = runTest { |
||||||
|
composition { |
||||||
|
RangeInput(value = 20, min = 10, max = 30, step = 2) { |
||||||
|
id("rangeInputId") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
val rangeInput = root.firstChild as HTMLInputElement |
||||||
|
|
||||||
|
assertEquals("range", rangeInput.getAttribute("type")) |
||||||
|
assertEquals("rangeInputId", rangeInput.getAttribute("id")) |
||||||
|
assertEquals("10", rangeInput.getAttribute("min")) |
||||||
|
assertEquals("30", rangeInput.getAttribute("max")) |
||||||
|
assertEquals("2", rangeInput.getAttribute("step")) |
||||||
|
assertEquals(20, rangeInput.valueAsNumber.toInt()) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun rangeInputWithDefaults() = runTest { |
||||||
|
composition { RangeInput() } |
||||||
|
|
||||||
|
val rangeInput = root.firstChild as HTMLInputElement |
||||||
|
|
||||||
|
assertEquals("range", rangeInput.getAttribute("type")) |
||||||
|
assertEquals(null, rangeInput.getAttribute("id")) |
||||||
|
assertEquals(null, rangeInput.getAttribute("min")) |
||||||
|
assertEquals(null, rangeInput.getAttribute("max")) |
||||||
|
assertEquals("1", rangeInput.getAttribute("step")) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun searchInput() = runTest { |
||||||
|
composition { |
||||||
|
SearchInput(value = "Search Term") { |
||||||
|
id("searchInputId") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
val searchInput = root.firstChild as HTMLInputElement |
||||||
|
|
||||||
|
assertEquals("search", searchInput.getAttribute("type")) |
||||||
|
assertEquals("searchInputId", searchInput.getAttribute("id")) |
||||||
|
assertEquals("Search Term", searchInput.value) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun searchInputWithDefaults() = runTest { |
||||||
|
composition { SearchInput() } |
||||||
|
|
||||||
|
val searchInput = root.firstChild as HTMLInputElement |
||||||
|
|
||||||
|
assertEquals("search", searchInput.getAttribute("type")) |
||||||
|
assertEquals(null, searchInput.getAttribute("id")) |
||||||
|
assertEquals("", searchInput.value) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun telInput() = runTest { |
||||||
|
composition { |
||||||
|
TelInput(value = "0123456789") { |
||||||
|
id("telInputId") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
val textInput = root.firstChild as HTMLInputElement |
||||||
|
|
||||||
|
assertEquals("tel", textInput.getAttribute("type")) |
||||||
|
assertEquals("telInputId", textInput.getAttribute("id")) |
||||||
|
assertEquals("0123456789", textInput.value) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun telInputWithDefaults() = runTest { |
||||||
|
composition { TelInput() } |
||||||
|
|
||||||
|
val textInput = root.firstChild as HTMLInputElement |
||||||
|
|
||||||
|
assertEquals("tel", textInput.getAttribute("type")) |
||||||
|
assertEquals(null, textInput.getAttribute("id")) |
||||||
|
assertEquals("", textInput.value) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun textInput() = runTest { |
||||||
|
composition { |
||||||
|
TextInput(value = "Some value") { |
||||||
|
id("textInputId") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
val textInput = root.firstChild as HTMLInputElement |
||||||
|
|
||||||
|
assertEquals("text", textInput.getAttribute("type")) |
||||||
|
assertEquals("textInputId", textInput.getAttribute("id")) |
||||||
|
assertEquals("Some value", textInput.value) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun textInputWithDefaults() = runTest { |
||||||
|
composition { TextInput() } |
||||||
|
|
||||||
|
val textInput = root.firstChild as HTMLInputElement |
||||||
|
|
||||||
|
assertEquals("text", textInput.getAttribute("type")) |
||||||
|
assertEquals(null, textInput.getAttribute("id")) |
||||||
|
assertEquals("", textInput.value) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun timeInput() = runTest { |
||||||
|
composition { |
||||||
|
TimeInput(value = "12:20") { |
||||||
|
id("timeInputId") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
val textInput = root.firstChild as HTMLInputElement |
||||||
|
|
||||||
|
assertEquals("time", textInput.getAttribute("type")) |
||||||
|
assertEquals("timeInputId", textInput.getAttribute("id")) |
||||||
|
assertEquals("12:20", textInput.value) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun timeInputWithDefaults() = runTest { |
||||||
|
composition { TimeInput() } |
||||||
|
|
||||||
|
val textInput = root.firstChild as HTMLInputElement |
||||||
|
|
||||||
|
assertEquals("time", textInput.getAttribute("type")) |
||||||
|
assertEquals(null, textInput.getAttribute("id")) |
||||||
|
assertEquals("", textInput.value) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun urlInput() = runTest { |
||||||
|
composition { |
||||||
|
UrlInput(value = "http://127.0.0.1") { |
||||||
|
id("urlInputId") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
val textInput = root.firstChild as HTMLInputElement |
||||||
|
|
||||||
|
assertEquals("url", textInput.getAttribute("type")) |
||||||
|
assertEquals("urlInputId", textInput.getAttribute("id")) |
||||||
|
assertEquals("http://127.0.0.1", textInput.value) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun urlInputWithDefaults() = runTest { |
||||||
|
composition { UrlInput() } |
||||||
|
|
||||||
|
val textInput = root.firstChild as HTMLInputElement |
||||||
|
|
||||||
|
assertEquals("url", textInput.getAttribute("type")) |
||||||
|
assertEquals(null, textInput.getAttribute("id")) |
||||||
|
assertEquals("", textInput.value) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun weekInput() = runTest { |
||||||
|
composition { |
||||||
|
WeekInput(value = "2017-W01") { |
||||||
|
id("weekInputId") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
val textInput = root.firstChild as HTMLInputElement |
||||||
|
|
||||||
|
assertEquals("week", textInput.getAttribute("type")) |
||||||
|
assertEquals("weekInputId", textInput.getAttribute("id")) |
||||||
|
assertEquals("2017-W01", textInput.value) |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
fun weekInputWithDefaults() = runTest { |
||||||
|
composition { WeekInput() } |
||||||
|
|
||||||
|
val textInput = root.firstChild as HTMLInputElement |
||||||
|
|
||||||
|
assertEquals("week", textInput.getAttribute("type")) |
||||||
|
assertEquals(null, textInput.getAttribute("id")) |
||||||
|
assertEquals("", textInput.value) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,106 @@ |
|||||||
|
package org.jetbrains.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.* |
||||||
|
import org.jetbrains.compose.web.css.* |
||||||
|
import org.jetbrains.compose.web.css.selectors.attr |
||||||
|
import org.jetbrains.compose.web.dom.* |
||||||
|
import org.jetbrains.compose.web.dom.ElementBuilder.Companion.Div |
||||||
|
|
||||||
|
class EventsTests { |
||||||
|
|
||||||
|
val doubleClickUpdatesText by testCase { |
||||||
|
var state by remember { mutableStateOf("") } |
||||||
|
|
||||||
|
TestText(state) |
||||||
|
|
||||||
|
Div( |
||||||
|
attrs = { |
||||||
|
id("box") |
||||||
|
style { |
||||||
|
width(100.px) |
||||||
|
height(100.px) |
||||||
|
backgroundColor("red") |
||||||
|
} |
||||||
|
onDoubleClick { state = "Double Click Works!" } |
||||||
|
} |
||||||
|
) { |
||||||
|
Text("Clickable Box") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
val focusInAndFocusOutUpdateTheText by testCase { |
||||||
|
var state by remember { mutableStateOf("") } |
||||||
|
|
||||||
|
TestText(state) |
||||||
|
|
||||||
|
Input(type = InputType.Text, attrs = { |
||||||
|
id("focusableInput") |
||||||
|
onFocusIn { |
||||||
|
state = "focused" |
||||||
|
} |
||||||
|
onFocusOut { |
||||||
|
state = "not focused" |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
val focusAndBlurUpdateTheText by testCase { |
||||||
|
var state by remember { mutableStateOf("") } |
||||||
|
|
||||||
|
TestText(state) |
||||||
|
|
||||||
|
Input(type = InputType.Text, attrs = { |
||||||
|
id("focusableInput") |
||||||
|
onFocus { |
||||||
|
state = "focused" |
||||||
|
} |
||||||
|
onBlur { |
||||||
|
state = "blured" |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
val scrollUpdatesText by testCase { |
||||||
|
var state by remember { mutableStateOf("") } |
||||||
|
|
||||||
|
TestText(state) |
||||||
|
|
||||||
|
Div( |
||||||
|
attrs = { |
||||||
|
id("box") |
||||||
|
style { |
||||||
|
property("overflow-y", "scroll") |
||||||
|
height(200.px) |
||||||
|
backgroundColor(Color.RGB(220, 220, 220)) |
||||||
|
} |
||||||
|
onScroll { |
||||||
|
state = "Scrolled" |
||||||
|
} |
||||||
|
} |
||||||
|
) { |
||||||
|
repeat(500) { |
||||||
|
P { |
||||||
|
Text("Scrollable content in Div - $it") |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
val selectEventUpdatesText by testCase { |
||||||
|
var state by remember { mutableStateOf("None") } |
||||||
|
|
||||||
|
P(attrs = { style { height(50.px) } }) { TestText(state) } |
||||||
|
|
||||||
|
Input(type = InputType.Text, attrs = { |
||||||
|
value("This is a text to be selected") |
||||||
|
id("selectableText") |
||||||
|
onSelect { |
||||||
|
state = "Text Selected" |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,174 @@ |
|||||||
|
/* |
||||||
|
* 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.interactions.Actions |
||||||
|
import org.openqa.selenium.WebDriver |
||||||
|
|
||||||
|
class EventTests : BaseIntegrationTests() { |
||||||
|
|
||||||
|
@ResolveDrivers |
||||||
|
fun `double click updates text`(driver: WebDriver) { |
||||||
|
driver.openTestPage("doubleClickUpdatesText") |
||||||
|
|
||||||
|
val box = driver.findElement(By.id("box")) |
||||||
|
|
||||||
|
val actions = Actions(driver) |
||||||
|
|
||||||
|
actions |
||||||
|
.doubleClick(box) |
||||||
|
.perform() |
||||||
|
|
||||||
|
driver.waitTextToBe(value = "Double Click Works!", textId = "txt") |
||||||
|
} |
||||||
|
|
||||||
|
@ResolveDrivers |
||||||
|
fun `focusin and focusout update the text`(driver: WebDriver) { |
||||||
|
driver.openTestPage("focusInAndFocusOutUpdateTheText") |
||||||
|
|
||||||
|
driver.waitTextToBe(value = "", textId = "txt") |
||||||
|
|
||||||
|
val input = driver.findElement(By.id("focusableInput")) |
||||||
|
|
||||||
|
val actions = Actions(driver) |
||||||
|
|
||||||
|
actions.moveToElement(input) |
||||||
|
.click() |
||||||
|
.perform() |
||||||
|
|
||||||
|
driver.waitTextToBe(value = "focused", textId = "txt") |
||||||
|
|
||||||
|
val actions2 = Actions(driver) |
||||||
|
|
||||||
|
actions2.moveToElement(driver.findElement(By.id("txt"))) |
||||||
|
.click() |
||||||
|
.perform() |
||||||
|
|
||||||
|
driver.waitTextToBe(value = "not focused", textId = "txt") |
||||||
|
} |
||||||
|
|
||||||
|
@ResolveDrivers |
||||||
|
fun `focus and blur update the text`(driver: WebDriver) { |
||||||
|
driver.openTestPage("focusAndBlurUpdateTheText") |
||||||
|
|
||||||
|
driver.waitTextToBe(value = "", textId = "txt") |
||||||
|
|
||||||
|
val input = driver.findElement(By.id("focusableInput")) |
||||||
|
|
||||||
|
val actions = Actions(driver) |
||||||
|
|
||||||
|
actions.moveToElement(input) |
||||||
|
.click() |
||||||
|
.perform() |
||||||
|
|
||||||
|
driver.waitTextToBe(value = "focused", textId = "txt") |
||||||
|
|
||||||
|
val actions2 = Actions(driver) |
||||||
|
|
||||||
|
actions2.moveToElement(driver.findElement(By.id("txt"))) |
||||||
|
.click() |
||||||
|
.perform() |
||||||
|
|
||||||
|
driver.waitTextToBe(value = "blured", textId = "txt") |
||||||
|
} |
||||||
|
|
||||||
|
@ResolveDrivers |
||||||
|
fun `scroll updates the text`(driver: WebDriver) { |
||||||
|
driver.openTestPage("scrollUpdatesText") |
||||||
|
|
||||||
|
driver.waitTextToBe(value = "", textId = "txt") |
||||||
|
|
||||||
|
val box = driver.findElement(By.id("box")) |
||||||
|
|
||||||
|
val actions = Actions(driver) |
||||||
|
actions.moveToElement(box) |
||||||
|
.click() |
||||||
|
.sendKeys(Keys.ARROW_DOWN) |
||||||
|
.perform() |
||||||
|
|
||||||
|
driver.waitTextToBe(value = "Scrolled", textId = "txt") |
||||||
|
} |
||||||
|
|
||||||
|
@ResolveDrivers |
||||||
|
fun `select event update the txt`(driver: WebDriver) { |
||||||
|
driver.openTestPage("selectEventUpdatesText") |
||||||
|
driver.waitTextToBe(value = "None") |
||||||
|
|
||||||
|
val selectableText = driver.findElement(By.id("selectableText")) |
||||||
|
|
||||||
|
val action = Actions(driver) |
||||||
|
|
||||||
|
action.moveToElement(selectableText,3,3) |
||||||
|
.click().keyDown(Keys.SHIFT) |
||||||
|
.moveToElement(selectableText,200, 0) |
||||||
|
.click().keyUp(Keys.SHIFT) |
||||||
|
.build() |
||||||
|
.perform() |
||||||
|
|
||||||
|
driver.waitTextToBe(value = "Text Selected") |
||||||
|
} |
||||||
|
|
||||||
|
@ResolveDrivers |
||||||
|
fun `stopImmediatePropagation prevents consequent listeners from being called`(driver: WebDriver) { |
||||||
|
driver.openTestPage("stopOnInputImmediatePropagationWorks") |
||||||
|
driver.waitTextToBe(value = "None") |
||||||
|
|
||||||
|
val checkBox = driver.findElement(By.id("checkbox")) |
||||||
|
val radioButtonToStopImmediatePropagation = driver.findElement(By.id("radioBtn")) |
||||||
|
|
||||||
|
checkBox.click() |
||||||
|
driver.waitTextToBe(value = "onInput2") |
||||||
|
|
||||||
|
radioButtonToStopImmediatePropagation.click() |
||||||
|
driver.waitTextToBe(value = "None") |
||||||
|
|
||||||
|
checkBox.click() |
||||||
|
driver.waitTextToBe(value = "onInput1") |
||||||
|
} |
||||||
|
|
||||||
|
@ResolveDrivers |
||||||
|
fun `preventDefault works as expected`(driver: WebDriver) { |
||||||
|
driver.openTestPage("preventDefaultWorks") |
||||||
|
|
||||||
|
driver.waitTextToBe(value = "None") |
||||||
|
driver.waitTextToBe(textId = "txt2", value = "None") |
||||||
|
|
||||||
|
val checkBox = driver.findElement(By.id("checkbox")) |
||||||
|
checkBox.click() |
||||||
|
|
||||||
|
driver.waitTextToBe(value = "Clicked but check should be prevented") |
||||||
|
driver.waitTextToBe(textId = "txt2", value = "None") |
||||||
|
} |
||||||
|
|
||||||
|
@ResolveDrivers |
||||||
|
fun `stopPropagation works as expected`(driver: WebDriver) { |
||||||
|
driver.openTestPage("stopPropagationWorks") |
||||||
|
|
||||||
|
driver.waitTextToBe(value = "None") |
||||||
|
driver.waitTextToBe(textId = "txt2", value = "None") |
||||||
|
|
||||||
|
val checkBox = driver.findElement(By.id("checkbox")) |
||||||
|
val radioButtonToStopImmediatePropagation = driver.findElement(By.id("radioBtn")) |
||||||
|
|
||||||
|
checkBox.click() |
||||||
|
driver.waitTextToBe(value = "childInput") |
||||||
|
driver.waitTextToBe(textId = "txt2", value = "div caught an input") |
||||||
|
|
||||||
|
radioButtonToStopImmediatePropagation.click() |
||||||
|
driver.waitTextToBe(value = "None") |
||||||
|
driver.waitTextToBe(textId = "txt2", value = "None") |
||||||
|
|
||||||
|
checkBox.click() |
||||||
|
driver.waitTextToBe(value = "childInput") |
||||||
|
driver.waitTextToBe(textId = "txt2", value = "None") |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue