UNPKG

@testing-library/user-event

Version:
118 lines (115 loc) 4.42 kB
import '../utils/click/isClickableInput.js'; import '../utils/dataTransfer/Clipboard.js'; import '../event/eventMap.js'; import '../event/behavior/click.js'; import '../event/behavior/cut.js'; import '../event/behavior/keydown.js'; import '../event/behavior/keypress.js'; import '../event/behavior/keyup.js'; import '../event/behavior/paste.js'; import '@testing-library/dom'; import { hasUISelection, setUISelection } from './selection.js'; import { prepareInterceptor } from './interceptor.js'; import '../utils/edit/maxLength.js'; import '../utils/edit/isEditable.js'; import { isElementType } from '../utils/misc/isElementType.js'; import { getWindow } from '../utils/misc/getWindow.js'; import '../utils/keyDef/readNextDescriptor.js'; import '../utils/misc/level.js'; import '../options.js'; const UIValue = Symbol('Displayed value in UI'); const InitialValue = Symbol('Initial value to compare on blur'); const TrackChanges = Symbol('Track programmatic changes for React workaround'); function valueInterceptor(v) { const isUI = typeof v === 'object' && v[UIValue]; if (isUI) { this[UIValue] = String(v); startTrackValue(this); } return { applyNative: !!isUI, realArgs: sanitizeValue(this, v), then: isUI ? undefined : ()=>trackOrSetValue(this, String(v)) }; } function sanitizeValue(element, v) { // Workaround for JSDOM if (isElementType(element, 'input', { type: 'number' }) && String(v) !== '' && !Number.isNaN(Number(v))) { // Setting value to "1." results in `null` in JSDOM return String(Number(v)); } return String(v); } function prepareValueInterceptor(element) { prepareInterceptor(element, 'value', valueInterceptor); } function setUIValue(element, value) { if (element[InitialValue] === undefined) { element[InitialValue] = element.value; } element.value = { [UIValue]: UIValue, toString: ()=>value }; } function getUIValue(element) { return element[UIValue] === undefined ? element.value : String(element[UIValue]); } /** Flag the IDL value as clean. This does not change the value.*/ function setUIValueClean(element) { element[UIValue] = undefined; } function clearInitialValue(element) { element[InitialValue] = undefined; } function getInitialValue(element) { return element[InitialValue]; } // When the input event happens in the browser, React executes all event handlers // and if they change state of a controlled value, nothing happens. // But when we trigger the event handlers in test environment with React@17, // the changes are rolled back before the state update is applied. // This results in a reset cursor. // There might be a better way to work around if we figure out // why the batched update is executed differently in our test environment. function isReact17Element(element) { return Object.getOwnPropertyNames(element).some((k)=>k.startsWith('__react')) && getWindow(element).REACT_VERSION === 17; } function startTrackValue(element) { if (!isReact17Element(element)) { return; } element[TrackChanges] = { previousValue: String(element.value), tracked: [] }; } function trackOrSetValue(element, v) { var ref, ref1; (ref = element[TrackChanges]) === null || ref === void 0 ? void 0 : (ref1 = ref.tracked) === null || ref1 === void 0 ? void 0 : ref1.push(v); if (!element[TrackChanges]) { setUIValueClean(element); setUISelection(element, { focusOffset: v.length }); } } function commitValueAfterInput(element, cursorOffset) { var ref; const changes = element[TrackChanges]; element[TrackChanges] = undefined; if (!(changes === null || changes === void 0 ? void 0 : (ref = changes.tracked) === null || ref === void 0 ? void 0 : ref.length)) { return; } const isJustReactStateUpdate = changes.tracked.length === 2 && changes.tracked[0] === changes.previousValue && changes.tracked[1] === element.value; if (!isJustReactStateUpdate) { setUIValueClean(element); } if (hasUISelection(element)) { setUISelection(element, { focusOffset: isJustReactStateUpdate ? cursorOffset : element.value.length }); } } export { clearInitialValue, commitValueAfterInput, getInitialValue, getUIValue, prepareValueInterceptor, setUIValue, setUIValueClean };