@testing-library/user-event
Version:
Fire events the same way the user does
118 lines (115 loc) • 4.42 kB
JavaScript
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 };