From 5be8ddec3d1226b5cf70d7feaabfed1b38b59610 Mon Sep 17 00:00:00 2001 From: Philipp Fritsche Date: Thu, 14 Jul 2022 09:58:30 +0000 Subject: [PATCH] fix(document): track `HTMLInputElement.setRangeText()` --- src/document/index.ts | 2 ++ src/document/selection.ts | 7 +++++++ src/document/setRangeText.ts | 21 +++++++++++++++++++++ src/document/value.ts | 7 +++++++ tests/document/index.ts | 19 +++++++++++++++++++ 5 files changed, 56 insertions(+) create mode 100644 src/document/setRangeText.ts diff --git a/src/document/index.ts b/src/document/index.ts index 70be9b06..9d63fa19 100644 --- a/src/document/index.ts +++ b/src/document/index.ts @@ -1,6 +1,7 @@ import {dispatchUIEvent} from '../event' import {Config} from '../setup' import {prepareSelectionInterceptor} from './selection' +import {prepareRangeTextInterceptor} from './setRangeText' import { clearInitialValue, getInitialValue, @@ -69,6 +70,7 @@ function prepareElement(el: Node | HTMLInputElement) { if ('value' in el) { prepareValueInterceptor(el) prepareSelectionInterceptor(el) + prepareRangeTextInterceptor(el) } el[isPrepared] = isPrepared diff --git a/src/document/selection.ts b/src/document/selection.ts index a40f7e18..8192dc2e 100644 --- a/src/document/selection.ts +++ b/src/document/selection.ts @@ -144,3 +144,10 @@ export function getUISelection( endOffset: Math.max(sel.anchorOffset, sel.focusOffset), } } + +/** Flag the IDL selection as clean. This does not change the selection. */ +export function setUISelectionClean( + element: HTMLInputElement | HTMLTextAreaElement, +) { + element[UISelection] = undefined +} diff --git a/src/document/setRangeText.ts b/src/document/setRangeText.ts new file mode 100644 index 00000000..3b92af52 --- /dev/null +++ b/src/document/setRangeText.ts @@ -0,0 +1,21 @@ +import {prepareInterceptor} from './interceptor' +import {setUISelectionClean} from './selection' +import {setUIValueClean} from './value' + +export function prepareRangeTextInterceptor( + element: HTMLInputElement | HTMLTextAreaElement, +) { + prepareInterceptor( + element, + 'setRangeText', + function interceptorImpl(...realArgs) { + return { + realArgs, + then: () => { + setUIValueClean(element) + setUISelectionClean(element) + }, + } + }, + ) +} diff --git a/src/document/value.ts b/src/document/value.ts index 0a410034..dd04024c 100644 --- a/src/document/value.ts +++ b/src/document/value.ts @@ -81,6 +81,13 @@ export function getUIValue(element: HTMLInputElement | HTMLTextAreaElement) { : String(element[UIValue]) } +/** Flag the IDL value as clean. This does not change the value.*/ +export function setUIValueClean( + element: HTMLInputElement | HTMLTextAreaElement, +) { + element[UIValue] = undefined +} + export function clearInitialValue( element: HTMLInputElement | HTMLTextAreaElement, ) { diff --git a/tests/document/index.ts b/tests/document/index.ts index f6b6268f..3f4679de 100644 --- a/tests/document/index.ts +++ b/tests/document/index.ts @@ -183,3 +183,22 @@ test('select input without selectionRange support', () => { expect(getUISelection(element)).toHaveProperty('startOffset', 0) expect(getUISelection(element)).toHaveProperty('endOffset', 3) }) + +test('track changes to value and selection per setRangeText', () => { + const {element} = render(``) + prepare(element) + setUIValue(element, 'abcd') + setUISelection(element, {focusOffset: 3}) + + element.setRangeText('X', 1, 2) + expect(element).toHaveValue('aXcd') + expect(element).toHaveProperty('selectionStart', 3) + expect(getUIValue(element)).toBe('aXcd') + expect(getUISelection(element)).toHaveProperty('focusOffset', 3) + + element.setRangeText('Y', 1, 2, 'start') + expect(element).toHaveValue('aYcd') + expect(element).toHaveProperty('selectionEnd', 1) + expect(getUIValue(element)).toBe('aYcd') + expect(getUISelection(element)).toHaveProperty('focusOffset', 1) +})