Skip to content

Commit b2fbf3e

Browse files
committed
fix: prevent undoing initial fill
1 parent 604d7f8 commit b2fbf3e

File tree

2 files changed

+94
-2
lines changed

2 files changed

+94
-2
lines changed

lib/components/Input/InputEditor.jsx

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { renderToStaticMarkup } from 'react-dom/server';
44
import { autocompletion, closeBrackets } from '@codemirror/autocomplete';
55
import { defaultKeymap, history, historyKeymap } from '@codemirror/commands';
66
import { bracketMatching, indentOnInput } from '@codemirror/language';
7-
import { Compartment, EditorState, Annotation } from '@codemirror/state';
7+
import { Compartment, EditorState, Annotation, Transaction } from '@codemirror/state';
88
import { EditorView, keymap, placeholder } from '@codemirror/view';
99
import { linter } from '@codemirror/lint';
1010
import { json, jsonParseLinter } from '@codemirror/lang-json';
@@ -66,6 +66,9 @@ export default function InputEditor({
6666

6767
const ref = useRef(null);
6868

69+
/** @type {import('react').MutableRefObject<EditorView | null>} */
70+
const initializedViewRef = useRef(null);
71+
6972
/**
7073
* @type {ReturnType<typeof useState<EditorView>>}
7174
*/
@@ -137,6 +140,10 @@ export default function InputEditor({
137140

138141
setEditorView(view);
139142

143+
if (value) {
144+
initializedViewRef.current = view;
145+
}
146+
140147
return () => {
141148
view.destroy();
142149
};
@@ -158,13 +165,21 @@ export default function InputEditor({
158165
const editorValue = editorView.state.doc.toString();
159166

160167
if (value !== editorValue) {
168+
const isInitialFill = initializedViewRef.current !== editorView;
169+
if (value) {
170+
initializedViewRef.current = editorView;
171+
}
172+
161173
editorView.dispatch({
162174
changes: {
163175
from: 0,
164176
to: editorValue.length,
165177
insert: value
166178
},
167-
annotations: fromPropAnnotation.of(true)
179+
annotations: [
180+
fromPropAnnotation.of(true),
181+
...isInitialFill ? [ Transaction.addToHistory.of(false) ] : []
182+
]
168183
});
169184
}
170185
}, [ editorView, value ]);

test/components/Input/InputEditor.spec.js

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,83 @@ describe('InputEditor', function() {
441441
});
442442
});
443443

444+
445+
it('should not undo the initial fill', async function() {
446+
447+
// given - editor starts without value, then receives async prefill
448+
const onChangeSpy = sinon.spy();
449+
const prefillValue = '{\n "foo": "bar"\n}';
450+
451+
const { container, getByRole, rerender } = renderWithProps({
452+
onChange: onChangeSpy
453+
});
454+
455+
// simulate async prefill arriving
456+
rerender(
457+
<InputEditor
458+
value={ prefillValue }
459+
onChange={ onChangeSpy }
460+
onErrorChange={ () => {} }
461+
/>
462+
);
463+
464+
await waitFor(() => {
465+
expect(container.textContent).to.contain('"foo": "bar"');
466+
});
467+
468+
// when - try to undo the prefill
469+
const textbox = getByRole('textbox');
470+
await user.click(textbox);
471+
await user.keyboard(undoKeys);
472+
473+
// then - prefill should remain (not undoable)
474+
await new Promise(resolve => setTimeout(resolve, 100));
475+
expect(onChangeSpy).to.not.have.been.called;
476+
expect(container.textContent).to.contain('"foo": "bar"');
477+
});
478+
479+
480+
it('should not undo past the initial value', async function() {
481+
482+
// given - editor starts with value, user types something
483+
const onChangeSpy = sinon.spy();
484+
const initialValue = '{\n "foo": "bar"\n}';
485+
486+
const { container, getByRole } = renderWithProps({
487+
value: initialValue,
488+
onChange: onChangeSpy
489+
});
490+
491+
const textbox = getByRole('textbox');
492+
await user.click(textbox);
493+
494+
// type something
495+
await user.keyboard('{ArrowRight}{Enter}"a": 1');
496+
497+
await waitFor(() => {
498+
expect(onChangeSpy).to.have.been.called;
499+
});
500+
501+
onChangeSpy.resetHistory();
502+
503+
// when - undo typing
504+
await user.keyboard(undoKeys);
505+
506+
await waitFor(() => {
507+
expect(onChangeSpy).to.have.been.calledWith(initialValue);
508+
});
509+
510+
onChangeSpy.resetHistory();
511+
512+
// when - undo again (should not go to empty)
513+
await user.keyboard(undoKeys);
514+
515+
// then - should stay at initial value
516+
await new Promise(resolve => setTimeout(resolve, 100));
517+
expect(onChangeSpy).to.not.have.been.called;
518+
expect(container.textContent).to.contain('"foo": "bar"');
519+
});
520+
444521
});
445522

446523
});

0 commit comments

Comments
 (0)