Skip to content

Commit

Permalink
feat: add support for custom elements (#320)
Browse files Browse the repository at this point in the history
* feat: add support for custom elements

Closes #319

* Update src/type.js

Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>

* uncomplicate setup fn

* make it untouched

Co-authored-by: Pontus Lundin <pontus.lundin@ica.se>
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
  • Loading branch information
3 people committed Jun 8, 2020
1 parent 5c11411 commit a55a657
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 3 deletions.
35 changes: 35 additions & 0 deletions src/__tests__/helpers/customElement.js
@@ -0,0 +1,35 @@
const observed = ['value']

class CustomEl extends HTMLElement {
static getObservedAttributes() {
return observed
}

constructor() {
super()
this.attachShadow({mode: 'open'})
this.shadowRoot.innerHTML = `<input>`
this.$input = this.shadowRoot.querySelector('input')
}

connectedCallback() {
observed.forEach(name => {
this.render(name, this.getAttribute(name))
})
}

attributeChangedCallback(name, oldVal, newVal) {
if (oldVal === newVal) return
this.render(name, newVal)
}

render(name, value) {
if (value == null) {
this.$input.removeAttribute(name)
} else {
this.$input.setAttribute(name, value)
}
}
}

customElements.define('custom-el', CustomEl)
28 changes: 27 additions & 1 deletion src/__tests__/type.js
@@ -1,7 +1,8 @@
import React, {Fragment} from 'react'
import {render, screen} from '@testing-library/react'
import userEvent from '..'
import {setup} from './helpers/utils'
import {setup, addListeners} from './helpers/utils'
import './helpers/customElement'

it('types text in input', async () => {
const {element, getEventCalls} = setup(<input />)
Expand All @@ -23,6 +24,31 @@ it('types text in input', async () => {
`)
})

it('types text inside custom element', async () => {
const {
container: {firstChild: customElement},
} = render(<custom-el />)
const inputEl = customElement.shadowRoot.querySelector('input')
const {getEventCalls} = addListeners(inputEl)

await userEvent.type(inputEl, 'Sup')
expect(getEventCalls()).toMatchInlineSnapshot(`
focus
keydown: S (83)
keypress: S (83)
input: "{CURSOR}" -> "S"
keyup: S (83)
keydown: u (117)
keypress: u (117)
input: "S{CURSOR}" -> "Su"
keyup: u (117)
keydown: p (112)
keypress: p (112)
input: "Su{CURSOR}" -> "Sup"
keyup: p (112)
`)
})

it('types text in textarea', async () => {
const {element, getEventCalls} = setup(<textarea />)
await userEvent.type(element, 'Sup')
Expand Down
13 changes: 11 additions & 2 deletions src/type.js
Expand Up @@ -17,14 +17,23 @@ async function type(...args) {
return result
}

const getActiveElement = document => {
const activeElement = document.activeElement
if (activeElement.shadowRoot) {
return getActiveElement(activeElement.shadowRoot) || activeElement
} else {
return activeElement
}
}

async function typeImpl(element, text, {allAtOnce = false, delay} = {}) {
if (element.disabled) return

element.focus()

// The focused element could change between each event, so get the currently active element each time
const currentElement = () => element.ownerDocument.activeElement
const currentValue = () => element.ownerDocument.activeElement.value
const currentElement = () => getActiveElement(element.ownerDocument)
const currentValue = () => currentElement().value
const setSelectionRange = newSelectionStart => {
// if the actual selection start is different from the one we expected
// then we set it to the end of the input
Expand Down

0 comments on commit a55a657

Please sign in to comment.