From ac76dc5f0e07ea77f4323b476a976cae52a2d192 Mon Sep 17 00:00:00 2001 From: Oleksandr Karpovich Date: Wed, 14 Jul 2021 12:22:36 +0200 Subject: [PATCH] web: Use SyntheticMouseEvent as a type for a value in mouse events listeners (#862) * web: Use SyntheticMouseEvent as a type for a value in mouse events listeners * add tests for mouse event Co-authored-by: Oleksandr Karpovich --- .../web/attributes/EventsListenerBuilder.kt | 199 +++++++++++------- .../web/attributes/InputAttrsBuilder.kt | 33 +-- .../web/attributes/TextAreaAttrsBuilder.kt | 2 +- .../compose/web/events/SyntheticEvent.kt | 24 +++ .../compose/web/events/SyntheticMouseEvent.kt | 67 ++++++ .../compose/web/events/WrappedEvent.kt | 4 +- .../compose/web/sample/tests/EventsTests.kt | 73 +++++++ .../web/tests/integration/EventTests.kt | 62 ++++++ 8 files changed, 360 insertions(+), 104 deletions(-) create mode 100644 web/core/src/jsMain/kotlin/org/jetbrains/compose/web/events/SyntheticEvent.kt create mode 100644 web/core/src/jsMain/kotlin/org/jetbrains/compose/web/events/SyntheticMouseEvent.kt diff --git a/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/attributes/EventsListenerBuilder.kt b/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/attributes/EventsListenerBuilder.kt index 31ac0119d2..82eb55679c 100644 --- a/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/attributes/EventsListenerBuilder.kt +++ b/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/attributes/EventsListenerBuilder.kt @@ -1,45 +1,148 @@ package org.jetbrains.compose.web.attributes -import org.jetbrains.compose.web.events.WrappedCheckBoxInputEvent +import androidx.compose.web.events.SyntheticDragEvent +import androidx.compose.web.events.SyntheticMouseEvent +import androidx.compose.web.events.SyntheticWheelEvent import org.jetbrains.compose.web.events.WrappedClipboardEvent -import org.jetbrains.compose.web.events.WrappedDragEvent import org.jetbrains.compose.web.events.WrappedEvent import org.jetbrains.compose.web.events.WrappedFocusEvent import org.jetbrains.compose.web.events.WrappedInputEvent import org.jetbrains.compose.web.events.WrappedKeyboardEvent -import org.jetbrains.compose.web.events.WrappedMouseEvent -import org.jetbrains.compose.web.events.WrappedRadioInputEvent -import org.jetbrains.compose.web.events.WrappedTextInputEvent import org.jetbrains.compose.web.events.WrappedTouchEvent -import org.jetbrains.compose.web.events.WrappedWheelEvent import org.jetbrains.compose.web.events.GenericWrappedEvent +private typealias SyntheticMouseEventListener = (SyntheticMouseEvent) -> Unit +private typealias SyntheticWheelEventListener = (SyntheticWheelEvent) -> Unit +private typealias SyntheticDragEventListener = (SyntheticDragEvent) -> Unit + open class EventsListenerBuilder { protected val listeners = mutableListOf>() - fun onCopy(options: Options = Options.DEFAULT, listener: (WrappedClipboardEvent) -> Unit) { - listeners.add(ClipboardEventListener(COPY, options, listener)) + /* Mouse Events */ + + private fun createMouseEventListener( + name: String, options: Options, listener: SyntheticMouseEventListener + ): MouseEventListener { + return MouseEventListener( + event = name, + options = options, + listener = { + listener(SyntheticMouseEvent(it.nativeEvent)) + } + ) } - fun onCut(options: Options = Options.DEFAULT, listener: (WrappedClipboardEvent) -> Unit) { - listeners.add(ClipboardEventListener(CUT, options, listener)) + private fun createMouseWheelEventListener( + name: String, options: Options, listener: SyntheticWheelEventListener + ): MouseWheelEventListener { + return MouseWheelEventListener( + event = name, + options = options, + listener = { + listener(SyntheticWheelEvent(it.nativeEvent)) + } + ) } - fun onPaste(options: Options = Options.DEFAULT, listener: (WrappedClipboardEvent) -> Unit) { - listeners.add(ClipboardEventListener(PASTE, options, listener)) + fun onContextMenu(options: Options = Options.DEFAULT, listener: SyntheticMouseEventListener) { + listeners.add(createMouseEventListener(CONTEXTMENU, options, listener)) + } + + fun onClick(options: Options = Options.DEFAULT, listener: SyntheticMouseEventListener) { + listeners.add(createMouseEventListener(CLICK, options, listener)) + } + + fun onDoubleClick(options: Options = Options.DEFAULT, listener: SyntheticMouseEventListener) { + listeners.add(createMouseEventListener(DBLCLICK, options, listener)) + } + + fun onMouseDown(options: Options = Options.DEFAULT, listener: SyntheticMouseEventListener) { + listeners.add(createMouseEventListener(MOUSEDOWN, options, listener)) + } + + fun onMouseUp(options: Options = Options.DEFAULT, listener: SyntheticMouseEventListener) { + listeners.add(createMouseEventListener(MOUSEUP, options, listener)) + } + + fun onMouseEnter(options: Options = Options.DEFAULT, listener: SyntheticMouseEventListener) { + listeners.add(createMouseEventListener(MOUSEENTER, options, listener)) + } + + fun onMouseLeave(options: Options = Options.DEFAULT, listener: SyntheticMouseEventListener) { + listeners.add(createMouseEventListener(MOUSELEAVE, options, listener)) + } + + fun onMouseMove(options: Options = Options.DEFAULT, listener: SyntheticMouseEventListener) { + listeners.add(createMouseEventListener(MOUSEMOVE, options, listener)) + } + + fun onMouseOut(options: Options = Options.DEFAULT, listener: SyntheticMouseEventListener) { + listeners.add(createMouseEventListener(MOUSEOUT, options, listener)) + } + + fun onMouseOver(options: Options = Options.DEFAULT, listener: SyntheticMouseEventListener) { + listeners.add(createMouseEventListener(MOUSEOVER, options, listener)) + } + + fun onWheel(options: Options = Options.DEFAULT, listener: (SyntheticWheelEvent) -> Unit) { + listeners.add(createMouseWheelEventListener(WHEEL, options, listener)) + } + + /* Drag Events */ + + private fun createDragEventListener( + name: String, options: Options, listener: SyntheticDragEventListener + ): DragEventListener { + return DragEventListener( + event = name, + options = options, + listener = { + listener(SyntheticDragEvent(it.nativeEvent)) + } + ) + } + + fun onDrag(options: Options = Options.DEFAULT, listener: SyntheticDragEventListener) { + listeners.add(createDragEventListener(DRAG, options, listener)) + } + + fun onDrop(options: Options = Options.DEFAULT, listener: SyntheticDragEventListener) { + listeners.add(createDragEventListener(DROP, options, listener)) + } + + fun onDragStart(options: Options = Options.DEFAULT, listener: SyntheticDragEventListener) { + listeners.add(createDragEventListener(DRAGSTART, options, listener)) + } + + fun onDragEnd(options: Options = Options.DEFAULT, listener: SyntheticDragEventListener) { + listeners.add(createDragEventListener(DRAGEND, options, listener)) + } + + fun onDragOver(options: Options = Options.DEFAULT, listener: SyntheticDragEventListener) { + listeners.add(createDragEventListener(DRAGOVER, options, listener)) } - fun onContextMenu(options: Options = Options.DEFAULT, listener: (WrappedMouseEvent) -> Unit) { - listeners.add(MouseEventListener(CONTEXTMENU, options, listener)) + fun onDragEnter(options: Options = Options.DEFAULT, listener: SyntheticDragEventListener) { + listeners.add(createDragEventListener(DRAGENTER, options, listener)) } - fun onClick(options: Options = Options.DEFAULT, listener: (WrappedMouseEvent) -> Unit) { - listeners.add(MouseEventListener(CLICK, options, listener)) + fun onDragLeave(options: Options = Options.DEFAULT, listener: SyntheticDragEventListener) { + listeners.add(createDragEventListener(DRAGLEAVE, options, listener)) } - fun onDoubleClick(options: Options = Options.DEFAULT, listener: (WrappedMouseEvent) -> Unit) { - listeners.add(MouseEventListener(DBLCLICK, options, listener)) + /* End of Drag Events */ + + fun onCopy(options: Options = Options.DEFAULT, listener: (WrappedClipboardEvent) -> Unit) { + listeners.add(ClipboardEventListener(COPY, options, listener)) + } + + fun onCut(options: Options = Options.DEFAULT, listener: (WrappedClipboardEvent) -> Unit) { + listeners.add(ClipboardEventListener(CUT, options, listener)) + } + + fun onPaste(options: Options = Options.DEFAULT, listener: (WrappedClipboardEvent) -> Unit) { + listeners.add(ClipboardEventListener(PASTE, options, listener)) } fun onGenericInput( @@ -85,38 +188,6 @@ open class EventsListenerBuilder { listeners.add(KeyboardEventListener(KEYUP, options, listener)) } - fun onMouseDown(options: Options = Options.DEFAULT, listener: (WrappedMouseEvent) -> Unit) { - listeners.add(MouseEventListener(MOUSEDOWN, options, listener)) - } - - fun onMouseUp(options: Options = Options.DEFAULT, listener: (WrappedMouseEvent) -> Unit) { - listeners.add(MouseEventListener(MOUSEUP, options, listener)) - } - - fun onMouseEnter(options: Options = Options.DEFAULT, listener: (WrappedMouseEvent) -> Unit) { - listeners.add(MouseEventListener(MOUSEENTER, options, listener)) - } - - fun onMouseLeave(options: Options = Options.DEFAULT, listener: (WrappedMouseEvent) -> Unit) { - listeners.add(MouseEventListener(MOUSELEAVE, options, listener)) - } - - fun onMouseMove(options: Options = Options.DEFAULT, listener: (WrappedMouseEvent) -> Unit) { - listeners.add(MouseEventListener(MOUSEMOVE, options, listener)) - } - - fun onMouseOut(options: Options = Options.DEFAULT, listener: (WrappedMouseEvent) -> Unit) { - listeners.add(MouseEventListener(MOUSEOUT, options, listener)) - } - - fun onMouseOver(options: Options = Options.DEFAULT, listener: (WrappedMouseEvent) -> Unit) { - listeners.add(MouseEventListener(MOUSEOVER, options, listener)) - } - - fun onWheel(options: Options = Options.DEFAULT, listener: (WrappedWheelEvent) -> Unit) { - listeners.add(MouseWheelEventListener(WHEEL, options, listener)) - } - fun onScroll(options: Options = Options.DEFAULT, listener: (WrappedEvent) -> Unit) { listeners.add(WrappedEventListener(SCROLL, options, listener)) } @@ -157,34 +228,6 @@ open class EventsListenerBuilder { listeners.add(InputEventListener(BEFOREINPUT, options, listener)) } - fun onDrag(options: Options = Options.DEFAULT, listener: (WrappedDragEvent) -> Unit) { - listeners.add(DragEventListener(DRAG, options, listener)) - } - - fun onDrop(options: Options = Options.DEFAULT, listener: (WrappedDragEvent) -> Unit) { - listeners.add(DragEventListener(DROP, options, listener)) - } - - fun onDragStart(options: Options = Options.DEFAULT, listener: (WrappedDragEvent) -> Unit) { - listeners.add(DragEventListener(DRAGSTART, options, listener)) - } - - fun onDragEnd(options: Options = Options.DEFAULT, listener: (WrappedDragEvent) -> Unit) { - listeners.add(DragEventListener(DRAGEND, options, listener)) - } - - fun onDragOver(options: Options = Options.DEFAULT, listener: (WrappedDragEvent) -> Unit) { - listeners.add(DragEventListener(DRAGOVER, options, listener)) - } - - fun onDragEnter(options: Options = Options.DEFAULT, listener: (WrappedDragEvent) -> Unit) { - listeners.add(DragEventListener(DRAGENTER, options, listener)) - } - - fun onDragLeave(options: Options = Options.DEFAULT, listener: (WrappedDragEvent) -> Unit) { - listeners.add(DragEventListener(DRAGLEAVE, options, listener)) - } - fun collectListeners(): List> = listeners fun addEventListener( diff --git a/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/attributes/InputAttrsBuilder.kt b/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/attributes/InputAttrsBuilder.kt index 37e0b438b2..d6ea224411 100644 --- a/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/attributes/InputAttrsBuilder.kt +++ b/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/attributes/InputAttrsBuilder.kt @@ -5,42 +5,29 @@ package androidx.compose.web.attributes +import androidx.compose.web.events.SyntheticEvent import org.jetbrains.compose.web.attributes.AttrsBuilder import org.jetbrains.compose.web.attributes.InputType import org.jetbrains.compose.web.attributes.Options -import org.w3c.dom.HTMLElement import org.w3c.dom.HTMLInputElement import org.w3c.dom.events.Event import org.w3c.dom.events.EventTarget -class SyntheticInputEvent( +class SyntheticInputEvent( val value: ValueType, - val target: Element, - val nativeEvent: Event -) { + nativeEvent: Event +) : SyntheticEvent( + nativeEvent = nativeEvent +) - val bubbles: Boolean = nativeEvent.bubbles - val cancelable: Boolean = nativeEvent.cancelable - val composed: Boolean = nativeEvent.composed - val currentTarget: HTMLElement? = nativeEvent.currentTarget.unsafeCast() - 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 = nativeEvent.composedPath() -} - -class InputAttrsBuilder(val inputType: InputType) : AttrsBuilder() { +class InputAttrsBuilder( + val inputType: InputType +) : AttrsBuilder() { fun onInput(options: Options = Options.DEFAULT, listener: (SyntheticInputEvent) -> Unit) { addEventListener(INPUT, options) { val value = inputType.inputValue(it.nativeEvent) - listener(SyntheticInputEvent(value, it.nativeEvent.target as HTMLInputElement, it.nativeEvent)) + listener(SyntheticInputEvent(value, it.nativeEvent)) } } } diff --git a/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/attributes/TextAreaAttrsBuilder.kt b/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/attributes/TextAreaAttrsBuilder.kt index 223230ccc9..76513a0856 100644 --- a/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/attributes/TextAreaAttrsBuilder.kt +++ b/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/attributes/TextAreaAttrsBuilder.kt @@ -17,7 +17,7 @@ class TextAreaAttrsBuilder : AttrsBuilder() { ) { addEventListener(INPUT, options) { val text = it.nativeEvent.target.asDynamic().value.unsafeCast() - listener(SyntheticInputEvent(text, it.nativeEvent.target as HTMLTextAreaElement, it.nativeEvent)) + listener(SyntheticInputEvent(text, it.nativeEvent)) } } } diff --git a/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/events/SyntheticEvent.kt b/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/events/SyntheticEvent.kt new file mode 100644 index 0000000000..d61cd91a71 --- /dev/null +++ b/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/events/SyntheticEvent.kt @@ -0,0 +1,24 @@ +package androidx.compose.web.events + +import org.w3c.dom.events.Event +import org.w3c.dom.events.EventTarget + +open class SyntheticEvent( + val nativeEvent: Event +) { + val target: Element = nativeEvent.target.unsafeCast() + val bubbles: Boolean = nativeEvent.bubbles + val cancelable: Boolean = nativeEvent.cancelable + val composed: Boolean = nativeEvent.composed + val currentTarget: EventTarget? = nativeEvent.currentTarget + 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 = nativeEvent.composedPath() +} diff --git a/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/events/SyntheticMouseEvent.kt b/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/events/SyntheticMouseEvent.kt new file mode 100644 index 0000000000..818b2cbdc6 --- /dev/null +++ b/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/events/SyntheticMouseEvent.kt @@ -0,0 +1,67 @@ +package androidx.compose.web.events + +import org.w3c.dom.DataTransfer +import org.w3c.dom.DragEvent +import org.w3c.dom.HTMLElement +import org.w3c.dom.events.EventTarget +import org.w3c.dom.events.MouseEvent +import org.w3c.dom.events.WheelEvent + +/** + * https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent + */ +open class SyntheticMouseEvent( + nativeEvent: MouseEvent +) : SyntheticEvent(nativeEvent) { + + private val mouseEvent = nativeEvent + + val altKey: Boolean = nativeEvent.altKey + val button: Short = nativeEvent.button + val buttons: Short = nativeEvent.buttons + val clientX: Int = nativeEvent.clientX + val clientY: Int = nativeEvent.clientY + val ctrlKey: Boolean = nativeEvent.ctrlKey + val metaKey: Boolean = nativeEvent.metaKey + val movementX: Int = nativeEvent.asDynamic().movementX as Int + val movementY: Int = nativeEvent.asDynamic().movementY as Int + val offsetX: Double = nativeEvent.offsetX + val offsetY: Double = nativeEvent.offsetY + val pageX: Double = nativeEvent.pageX + val pageY: Double = nativeEvent.pageY + val region: String? = nativeEvent.region + val relatedTarget: EventTarget? = nativeEvent.relatedTarget + val screenX: Int = nativeEvent.screenX + val screenY: Int = nativeEvent.screenY + val shiftKey: Boolean = nativeEvent.shiftKey + val x: Double = nativeEvent.x + val y: Double = nativeEvent.y + + fun getModifierState(keyArg: String): Boolean = mouseEvent.getModifierState(keyArg) +} + + +/** + * https://developer.mozilla.org/en-US/docs/Web/API/WheelEvent + */ +class SyntheticWheelEvent( + nativeEvent: WheelEvent +) : SyntheticMouseEvent( + nativeEvent +) { + val deltaX: Double = nativeEvent.deltaX + val deltaY: Double = nativeEvent.deltaY + val deltaZ: Double = nativeEvent.deltaZ + val deltaMode: Int = nativeEvent.deltaMode +} + +/** + * https://developer.mozilla.org/en-US/docs/Web/API/DragEvent + */ +class SyntheticDragEvent( + nativeEvent: DragEvent +) : SyntheticMouseEvent( + nativeEvent +) { + val dataTransfer: DataTransfer? = nativeEvent.dataTransfer +} diff --git a/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/events/WrappedEvent.kt b/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/events/WrappedEvent.kt index 4c967234ce..3994204b84 100644 --- a/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/events/WrappedEvent.kt +++ b/web/core/src/jsMain/kotlin/org/jetbrains/compose/web/events/WrappedEvent.kt @@ -18,7 +18,7 @@ interface GenericWrappedEvent { interface WrappedEvent : GenericWrappedEvent -open class WrappedMouseEvent( +internal open class WrappedMouseEvent( override val nativeEvent: MouseEvent ) : GenericWrappedEvent { @@ -29,7 +29,7 @@ open class WrappedMouseEvent( get() = nativeEvent.asDynamic().movementY as Double } -open class WrappedWheelEvent( +internal open class WrappedWheelEvent( override val nativeEvent: WheelEvent ) : GenericWrappedEvent diff --git a/web/integration-core/src/jsMain/kotlin/androidx/compose/web/sample/tests/EventsTests.kt b/web/integration-core/src/jsMain/kotlin/androidx/compose/web/sample/tests/EventsTests.kt index 65a8ba832b..f467126124 100644 --- a/web/integration-core/src/jsMain/kotlin/androidx/compose/web/sample/tests/EventsTests.kt +++ b/web/integration-core/src/jsMain/kotlin/androidx/compose/web/sample/tests/EventsTests.kt @@ -101,4 +101,77 @@ class EventsTests { } }) } + + val mouseEnterPlusExtraButtonsPressedUpdatesText by testCase { + var state by remember { mutableStateOf("None") } + + P( + attrs = { + style { height(50.px) } + } + ) { TestText(state) } + + Div(attrs = { + id("box") + style { + backgroundColor("red") + padding(50.px) + width(300.px) + } + onMouseEnter { + val buttonsPressed = mutableListOf() + if (it.altKey) buttonsPressed.add("ALT") + if (it.ctrlKey) buttonsPressed.add("CTRL") + if (it.shiftKey) buttonsPressed.add("SHIFT") + if (it.metaKey) buttonsPressed.add("META") + + state = "ENTERED+${buttonsPressed.joinToString(separator = ",")}" + } + + }) { + Text("Enter mouse over me with buttons pressed (META, ALT, SHIFT, CTRL)") + } + } + + val onMouseContextMenuUpdatesText by testCase { + var state by remember { mutableStateOf("None") } + + P( + attrs = { + id("box") + style { height(50.px) } + onContextMenu { + if (it.button == 2.toShort()) { + it.preventDefault() + it.stopImmediatePropagation() + state = "MOUSE CONTEXT MENU" + } + } + } + ) { TestText(state) } + } + + val displayMouseCoordinates by testCase { + var state by remember { mutableStateOf("None") } + + Div( + attrs = { + id("box") + style { + width(200.px) + height(200.px) + backgroundColor("red") + } + onMouseMove { + state = "${it.x},${it.y}|${it.offsetX},${it.offsetY}" + } + } + ) + + P( + attrs = { + style { height(50.px) } + } + ) { TestText(state) } + } } diff --git a/web/integration-core/src/jvmTest/kotlin/org/jetbrains/compose/web/tests/integration/EventTests.kt b/web/integration-core/src/jvmTest/kotlin/org/jetbrains/compose/web/tests/integration/EventTests.kt index a762156a0f..8daf8328b0 100644 --- a/web/integration-core/src/jvmTest/kotlin/org/jetbrains/compose/web/tests/integration/EventTests.kt +++ b/web/integration-core/src/jvmTest/kotlin/org/jetbrains/compose/web/tests/integration/EventTests.kt @@ -174,4 +174,66 @@ class EventTests : BaseIntegrationTests() { driver.waitTextToBe(value = "childInput") driver.waitTextToBe(textId = "txt2", value = "None") } + + @ResolveDrivers + fun mouseEnterPlusExtraButtonsPressedUpdatesText(driver: WebDriver) { + driver.openTestPage("mouseEnterPlusExtraButtonsPressedUpdatesText") + driver.waitTextToBe(value = "None") + + val box = driver.findElement(By.id("box")) + + Actions(driver).moveToElement(box).perform() + + driver.waitTextToBe(value = "ENTERED+") + + Actions(driver).moveByOffset(0, 100) + .keyDown(Keys.CONTROL) + .moveToElement(box) + .keyUp(Keys.CONTROL) + .perform() + + driver.waitTextToBe(value = "ENTERED+CTRL") + + Actions(driver).moveByOffset(0, 100) + .keyDown(Keys.SHIFT) + .moveToElement(box) + .keyUp(Keys.SHIFT) + .perform() + + driver.waitTextToBe(value = "ENTERED+SHIFT") + + Actions(driver).moveByOffset(0, 100) + .keyDown(Keys.ALT) + .moveToElement(box) + .keyUp(Keys.ALT) + .perform() + + driver.waitTextToBe(value = "ENTERED+ALT") + } + + @ResolveDrivers + fun onMouseContextMenuUpdatesText(driver: WebDriver) { + driver.openTestPage("onMouseContextMenuUpdatesText") + driver.waitTextToBe(value = "None") + + val box = driver.findElement(By.id("box")) + + Actions(driver).contextClick(box).perform() + + driver.waitTextToBe(value = "MOUSE CONTEXT MENU") + } + + @ResolveDrivers + fun displayMouseCoordinates(driver: WebDriver) { + driver.openTestPage("displayMouseCoordinates") + driver.waitTextToBe(value = "None") + + val box = driver.findElement(By.id("box")) + + Actions(driver).moveToElement(box).perform() + driver.waitTextToBe(value = "108,108|100,100") + + Actions(driver).moveToElement(box).moveByOffset(-20, -20).perform() + driver.waitTextToBe(value = "88,88|80,80") + } }