|
|
@@ -217,10 +217,12 @@ function bench(over?: BenchOptions) {
|
|
|
/**
|
|
|
* Dispatch the native `beforeinput` the composer reads the pre-edit selection
|
|
|
* from. The DOM event carries no range for a textarea (`getTargetRanges()` is
|
|
|
- * empty there), so the element's own selection at dispatch time is the signal.
|
|
|
+ * empty there), so the element's own selection plus `inputType` is the signal.
|
|
|
+ * The selection each gesture leaves is the engine-observed one: a delete over a
|
|
|
+ * selection reports that selection, a caret delete reports the bare caret.
|
|
|
*/
|
|
|
-function beforeInput(el: HTMLTextAreaElement): void {
|
|
|
- el.dispatchEvent(new Event('beforeinput', { bubbles: true, cancelable: true }))
|
|
|
+function beforeInput(el: HTMLTextAreaElement, inputType = 'insertText'): void {
|
|
|
+ el.dispatchEvent(new InputEvent('beforeinput', { bubbles: true, cancelable: true, inputType }))
|
|
|
}
|
|
|
|
|
|
function attachmentOwner(slotCalls: readonly { key: string; owner: unknown }[]): ComposerAttachmentsOwnerProps {
|
|
|
@@ -1256,7 +1258,7 @@ describe('decorations', () => {
|
|
|
expect(shell.snapshot.occurrences[0]).toMatchObject({ offset: 1, length: 4 })
|
|
|
})
|
|
|
|
|
|
- it('deleting the char before a reference keeps it structured when both are the trigger', () => {
|
|
|
+ it('a selection-replacing delete before a reference keeps it structured', () => {
|
|
|
const { shell, textarea } = bench()
|
|
|
act(() => {
|
|
|
shell.setDraft('@@w1')
|
|
|
@@ -1267,7 +1269,66 @@ describe('decorations', () => {
|
|
|
expect(shell.snapshot.draft).toBe('@@会话一 ')
|
|
|
textarea.setSelectionRange(0, 1)
|
|
|
act(() => {
|
|
|
- beforeInput(textarea)
|
|
|
+ beforeInput(textarea, 'deleteContentBackward')
|
|
|
+ fireEvent.change(textarea, { target: { value: '@会话一 ' } })
|
|
|
+ })
|
|
|
+ expect(shell.snapshot.draft).toBe('@会话一 ')
|
|
|
+ expect(shell.snapshot.occurrences).toHaveLength(1)
|
|
|
+ expect(shell.snapshot.occurrences[0]).toMatchObject({ offset: 0, length: 4 })
|
|
|
+ })
|
|
|
+
|
|
|
+ it('a caret Backspace before a reference keeps it structured', () => {
|
|
|
+ const { shell, textarea } = bench()
|
|
|
+ act(() => {
|
|
|
+ shell.setDraft('@@w1')
|
|
|
+ shell.insertReference({
|
|
|
+ source: 'reference', ref: 'w1', label: '会话一', appearance: 'session', clipboardText: '@w1',
|
|
|
+ }, { start: 1, end: 4, draftRev: shell.snapshot.draftRev })
|
|
|
+ })
|
|
|
+ expect(shell.snapshot.draft).toBe('@@会话一 ')
|
|
|
+ // A caret delete reports the bare caret, never the character it removes.
|
|
|
+ textarea.setSelectionRange(1, 1)
|
|
|
+ act(() => {
|
|
|
+ beforeInput(textarea, 'deleteContentBackward')
|
|
|
+ fireEvent.change(textarea, { target: { value: '@会话一 ' } })
|
|
|
+ })
|
|
|
+ expect(shell.snapshot.draft).toBe('@会话一 ')
|
|
|
+ expect(shell.snapshot.occurrences).toHaveLength(1)
|
|
|
+ expect(shell.snapshot.occurrences[0]).toMatchObject({ offset: 0, length: 4 })
|
|
|
+ })
|
|
|
+
|
|
|
+ it('a caret Delete before a reference keeps it structured', () => {
|
|
|
+ const { shell, textarea } = bench()
|
|
|
+ act(() => {
|
|
|
+ shell.setDraft('@@w1')
|
|
|
+ shell.insertReference({
|
|
|
+ source: 'reference', ref: 'w1', label: '会话一', appearance: 'session', clipboardText: '@w1',
|
|
|
+ }, { start: 1, end: 4, draftRev: shell.snapshot.draftRev })
|
|
|
+ })
|
|
|
+ textarea.setSelectionRange(0, 0)
|
|
|
+ act(() => {
|
|
|
+ beforeInput(textarea, 'deleteContentForward')
|
|
|
+ fireEvent.change(textarea, { target: { value: '@会话一 ' } })
|
|
|
+ })
|
|
|
+ expect(shell.snapshot.draft).toBe('@会话一 ')
|
|
|
+ expect(shell.snapshot.occurrences).toHaveLength(1)
|
|
|
+ expect(shell.snapshot.occurrences[0]).toMatchObject({ offset: 0, length: 4 })
|
|
|
+ })
|
|
|
+
|
|
|
+ it('a caret word delete before a reference keeps it structured', () => {
|
|
|
+ const { shell, textarea } = bench()
|
|
|
+ act(() => {
|
|
|
+ shell.setDraft('word @w1')
|
|
|
+ shell.insertReference({
|
|
|
+ source: 'reference', ref: 'w1', label: '会话一', appearance: 'session', clipboardText: '@w1',
|
|
|
+ }, { start: 5, end: 8, draftRev: shell.snapshot.draftRev })
|
|
|
+ })
|
|
|
+ expect(shell.snapshot.draft).toBe('word @会话一 ')
|
|
|
+ // One caret gesture can remove more than one character; the deleted span
|
|
|
+ // is whatever the draft lost, never a fixed step.
|
|
|
+ textarea.setSelectionRange(5, 5)
|
|
|
+ act(() => {
|
|
|
+ beforeInput(textarea, 'deleteWordBackward')
|
|
|
fireEvent.change(textarea, { target: { value: '@会话一 ' } })
|
|
|
})
|
|
|
expect(shell.snapshot.draft).toBe('@会话一 ')
|