Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: convert exported modules to typescript #599

Merged
merged 4 commits into from Mar 19, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 0 additions & 1 deletion src/clear.d.ts

This file was deleted.

30 changes: 0 additions & 30 deletions src/clear.js

This file was deleted.

43 changes: 43 additions & 0 deletions src/clear.ts
@@ -0,0 +1,43 @@
import {isDisabled, isInstanceOfElement} from './utils'
import {type} from './type'

function clear(element: Element) {
if (
!isInstanceOfElement(element, 'HTMLInputElement') &&
!isInstanceOfElement(element, 'HTMLTextAreaElement')
) {
// TODO: support contenteditable
throw new Error(
'clear currently only supports input and textarea elements.',
)
}
const el = element as HTMLInputElement | HTMLTextAreaElement

if (isDisabled(el)) {
return
}

// TODO: track the selection range ourselves so we don't have to do this input "type" trickery
// just like cypress does: https://github.com/cypress-io/cypress/blob/8d7f1a0bedc3c45a2ebf1ff50324b34129fdc683/packages/driver/src/dom/selection.ts#L16-L37

const elementType = el.type

if (elementType !== 'textarea') {
// setSelectionRange is not supported on certain types of inputs, e.g. "number" or "email"
;(element as HTMLInputElement).type = 'text'
}

type(element, '{selectall}{del}', {
delay: 0,
initialSelectionStart:
el.selectionStart ?? /* istanbul ignore next */ undefined,
initialSelectionEnd:
el.selectionEnd ?? /* istanbul ignore next */ undefined,
})

if (elementType !== 'textarea') {
;(el as HTMLInputElement).type = elementType
}
}

export {clear}
12 changes: 0 additions & 12 deletions src/click.d.ts

This file was deleted.

75 changes: 49 additions & 26 deletions src/click.js → src/click.ts
Expand Up @@ -3,12 +3,14 @@ import {
getMouseEventOptions,
isLabelWithInternallyDisabledControl,
isFocusable,
isDisabled,
isInstanceOfElement,
} from './utils'
import {hover} from './hover'
import {blur} from './blur'
import {focus} from './focus'

function getPreviouslyFocusedElement(element) {
function getPreviouslyFocusedElement(element: Element) {
const focusedElement = element.ownerDocument.activeElement
const wasAnotherElementFocused =
focusedElement &&
Expand All @@ -17,7 +19,16 @@ function getPreviouslyFocusedElement(element) {
return wasAnotherElementFocused ? focusedElement : null
}

function clickLabel(label, init, {clickCount}) {
export declare interface clickOptions {
skipHover?: boolean
clickCount?: number
}

function clickLabel(
label: HTMLLabelElement,
init: MouseEventInit | undefined,
{clickCount}: clickOptions,
) {
if (isLabelWithInternallyDisabledControl(label)) return

fireEvent.pointerDown(label, init)
Expand All @@ -34,15 +45,19 @@ function clickLabel(label, init, {clickCount}) {
if (label.control) focus(label.control)
}

function clickBooleanElement(element, init, clickCount) {
function clickBooleanElement(
element: HTMLInputElement,
init: MouseEventInit | undefined,
{clickCount}: clickOptions,
) {
fireEvent.pointerDown(element, init)
if (!element.disabled) {
fireEvent.mouseDown(
element,
getMouseEventOptions('mousedown', init, clickCount),
)
}
focus(element, init)
focus(element)
fireEvent.pointerUp(element, init)
if (!element.disabled) {
fireEvent.mouseUp(
Expand All @@ -53,36 +68,41 @@ function clickBooleanElement(element, init, clickCount) {
}
}

function clickElement(element, init, {clickCount}) {
function clickElement(
element: Element,
init: MouseEventInit | undefined,
{clickCount}: clickOptions,
) {
const previousElement = getPreviouslyFocusedElement(element)
fireEvent.pointerDown(element, init)
if (!element.disabled) {
if (!isDisabled(element)) {
const continueDefaultHandling = fireEvent.mouseDown(
element,
getMouseEventOptions('mousedown', init, clickCount),
)
if (continueDefaultHandling) {
const closestFocusable = findClosest(element, isFocusable)
if (previousElement && !closestFocusable) {
blur(previousElement, init)
blur(previousElement)
} else if (closestFocusable) {
focus(closestFocusable, init)
focus(closestFocusable)
}
}
}
fireEvent.pointerUp(element, init)
if (!element.disabled) {
if (!isDisabled(element)) {
fireEvent.mouseUp(
element,
getMouseEventOptions('mouseup', init, clickCount),
)
fireEvent.click(element, getMouseEventOptions('click', init, clickCount))
const parentLabel = element.closest('label')
if (parentLabel?.control) focus(parentLabel.control, init)
if (parentLabel?.control) focus(parentLabel.control)
}
}

function findClosest(el, callback) {
function findClosest(element: Element, callback: (e: Element) => boolean) {
let el: Element | null = element
do {
if (callback(el)) {
return el
Expand All @@ -92,25 +112,28 @@ function findClosest(el, callback) {
return undefined
}

function click(element, init, {skipHover = false, clickCount = 0} = {}) {
function click(
element: Element,
init?: MouseEventInit,
{skipHover = false, clickCount = 0}: clickOptions = {},
) {
if (!skipHover) hover(element, init)
switch (element.tagName) {
case 'LABEL':
clickLabel(element, init, {clickCount})
break
case 'INPUT':
if (element.type === 'checkbox' || element.type === 'radio') {
clickBooleanElement(element, init, {clickCount})
} else {
clickElement(element, init, {clickCount})
}
break
default:
clickElement(element, init, {clickCount})

if (isInstanceOfElement(element, 'HTMLLabelElement')) {
clickLabel(element as HTMLLabelElement, init, {clickCount})
} else if (isInstanceOfElement(element, 'HTMLInputElement')) {
const el = element as HTMLInputElement
if (el.type === 'checkbox' || el.type === 'radio') {
clickBooleanElement(el, init, {clickCount})
} else {
clickElement(el, init, {clickCount})
}
} else {
clickElement(element, init, {clickCount})
}
}

function dblClick(element, init) {
function dblClick(element: Element, init?: MouseEventInit) {
hover(element, init)
click(element, init, {skipHover: true, clickCount: 0})
click(element, init, {skipHover: true, clickCount: 1})
Expand Down
9 changes: 0 additions & 9 deletions src/paste.d.ts

This file was deleted.

34 changes: 25 additions & 9 deletions src/paste.js → src/paste.ts
Expand Up @@ -3,15 +3,25 @@ import {
setSelectionRangeIfNecessary,
calculateNewValue,
eventWrapper,
isDisabled,
} from './utils'

interface pasteOptions {
initialSelectionStart?: number
initialSelectionEnd?: number
}

function paste(
element,
text,
init,
{initialSelectionStart, initialSelectionEnd} = {},
element: HTMLInputElement | HTMLTextAreaElement,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This type definition makes it impossible to use elements returned by RTS queries, without type casting.

see: #648

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's a PR to fix this: #649

text: string,
init?: MouseEventInit,
{initialSelectionStart, initialSelectionEnd}: pasteOptions = {},
) {
if (element.disabled) return
if (isDisabled(element)) {
return
}

// TODO: implement for contenteditable
if (typeof element.value === 'undefined') {
throw new TypeError(
`the current element is of type ${element.tagName} and doesn't have a valid value`,
Expand Down Expand Up @@ -43,10 +53,16 @@ function paste(
inputType: 'insertFromPaste',
target: {value: newValue},
})
setSelectionRangeIfNecessary(element, {
newSelectionStart,
newSelectionEnd: newSelectionStart,
})
setSelectionRangeIfNecessary(
element,

// TODO: investigate why the selection caused by invalid parameters was expected
({
newSelectionStart,
selectionEnd: newSelectionStart,
} as unknown) as number,
({} as unknown) as number,
)
}
}

Expand Down
11 changes: 0 additions & 11 deletions src/select-options.d.ts

This file was deleted.

33 changes: 20 additions & 13 deletions src/select-options.js → src/select-options.ts
@@ -1,11 +1,16 @@
import {createEvent, getConfig, fireEvent} from '@testing-library/dom'
import {isInstanceOfElement} from './utils'
import {isDisabled, isInstanceOfElement} from './utils'
import {click} from './click'
import {focus} from './focus'
import {hover, unhover} from './hover'

function selectOptionsBase(newValue, select, values, init) {
if (!newValue && !select.multiple) {
function selectOptionsBase(
newValue: boolean,
select: Element,
values: HTMLElement | HTMLElement[] | string[] | string,
init?: MouseEventInit,
) {
if (!newValue && !(select as HTMLSelectElement).multiple) {
throw getConfig().getElementError(
`Unable to deselect an option in a non-multiple select. Use selectOptions to change the selection instead.`,
select,
Expand All @@ -17,28 +22,30 @@ function selectOptionsBase(newValue, select, values, init) {
)
const selectedOptions = valArray
.map(val => {
if (allOptions.includes(val)) {
if (typeof val !== 'string' && allOptions.includes(val)) {
return val
} else {
const matchingOption = allOptions.find(
o => o.value === val || o.innerHTML === val,
o =>
(o as HTMLInputElement | HTMLTextAreaElement).value === val ||
o.innerHTML === val,
)
if (matchingOption) {
return matchingOption
} else {
throw getConfig().getElementError(
`Value "${val}" not found in options`,
`Value "${String(val)}" not found in options`,
select,
)
}
}
})
.filter(option => !option.disabled)
.filter(option => !isDisabled(option))

if (select.disabled || !selectedOptions.length) return
if (isDisabled(select) || !selectedOptions.length) return

if (isInstanceOfElement(select, 'HTMLSelectElement')) {
if (select.multiple) {
if ((select as HTMLSelectElement).multiple) {
for (const option of selectedOptions) {
// events fired for multiple select are weird. Can't use hover...
fireEvent.pointerOver(option, init)
Expand All @@ -49,17 +56,17 @@ function selectOptionsBase(newValue, select, values, init) {
fireEvent.mouseMove(option, init)
fireEvent.pointerDown(option, init)
fireEvent.mouseDown(option, init)
focus(select, init)
focus(select)
fireEvent.pointerUp(option, init)
fireEvent.mouseUp(option, init)
selectOption(option)
selectOption(option as HTMLOptionElement)
fireEvent.click(option, init)
}
} else if (selectedOptions.length === 1) {
// the click to open the select options
click(select, init)

selectOption(selectedOptions[0])
selectOption(selectedOptions[0] as HTMLOptionElement)

// the browser triggers another click event on the select for the click on the option
// this second click has no 'down' phase
Expand Down Expand Up @@ -89,7 +96,7 @@ function selectOptionsBase(newValue, select, values, init) {
)
}

function selectOption(option) {
function selectOption(option: HTMLOptionElement) {
option.selected = newValue
fireEvent(
select,
Expand Down