Skip to content

Commit

Permalink
feat: add support for custom elements
Browse files Browse the repository at this point in the history
  • Loading branch information
Pontus Lundin committed Jun 8, 2020
1 parent 5c11411 commit b658abe
Show file tree
Hide file tree
Showing 5 changed files with 81 additions and 6 deletions.
2 changes: 1 addition & 1 deletion README.md
Expand Up @@ -50,7 +50,6 @@ change the state of the checkbox.
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->


- [Installation](#installation)
- [API](#api)
- [`click(element)`](#clickelement)
Expand Down Expand Up @@ -476,6 +475,7 @@ Thanks goes to these people ([emoji key][emojis]):

<!-- markdownlint-enable -->
<!-- prettier-ignore-end -->

<!-- ALL-CONTRIBUTORS-LIST:END -->

This project follows the [all-contributors][all-contributors] specification.
Expand Down
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)
13 changes: 10 additions & 3 deletions src/__tests__/helpers/utils.js
Expand Up @@ -37,14 +37,21 @@ function addEventListener(el, type, listener, options) {
el.addEventListener(type, hijackedListener, options)
}

function setup(ui) {
const {
function setup(ui, {shadowRootSelector} = {}) {
let hostElement
let {
container: {firstChild: element},
} = render(ui)

if (shadowRootSelector) {
hostElement = element
element = element.shadowRoot.querySelector(shadowRootSelector)
}

element.previousTestData = getTestData(element)

const {getEventCalls, clearEventCalls} = addListeners(element)
return {element, getEventCalls, clearEventCalls}
return {element, hostElement, getEventCalls, clearEventCalls}
}

function addListeners(element) {
Expand Down
24 changes: 24 additions & 0 deletions src/__tests__/type.js
Expand Up @@ -2,6 +2,7 @@ import React, {Fragment} from 'react'
import {render, screen} from '@testing-library/react'
import userEvent from '..'
import {setup} from './helpers/utils'
import './helpers/customElement'

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

it('types text inside custom element', async () => {
const {element, hostElement, getEventCalls} = setup(<custom-el />, {
shadowRootSelector: 'input',
})

await userEvent.type(element, 'Sup', {hostElement})
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 = element => {
const activeElement = element.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 b658abe

Please sign in to comment.