From d0442602b6c1fafdee41cb5ef764e67397c8ef1c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 19 Feb 2026 11:43:19 +0000 Subject: [PATCH 1/3] Initial plan From 9e4720ba0ea0189fd80d131d6e30df8c7b63cc5d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 19 Feb 2026 11:47:10 +0000 Subject: [PATCH 2/3] Add CodeMirror history extension to enable undo/redo for clear operation Co-authored-by: Buckwich <7346215+Buckwich@users.noreply.github.com> --- lib/components/Input/InputEditor.jsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/components/Input/InputEditor.jsx b/lib/components/Input/InputEditor.jsx index ee05584..213f9f5 100644 --- a/lib/components/Input/InputEditor.jsx +++ b/lib/components/Input/InputEditor.jsx @@ -2,7 +2,7 @@ import React, { useEffect, useMemo, useRef, useState } from 'react'; import { renderToStaticMarkup } from 'react-dom/server'; import { autocompletion, closeBrackets } from '@codemirror/autocomplete'; -import { defaultKeymap } from '@codemirror/commands'; +import { defaultKeymap, history, historyKeymap } from '@codemirror/commands'; import { bracketMatching, indentOnInput } from '@codemirror/language'; import { Compartment, EditorState, Annotation } from '@codemirror/state'; import { EditorView, keymap, placeholder } from '@codemirror/view'; @@ -96,11 +96,13 @@ export default function InputEditor({ const editorState = EditorState.create({ doc: value, extensions: [ + history(), autocompletion(), closeBrackets(), bracketMatching(), indentOnInput(), keymap.of([ + ...historyKeymap, ...defaultKeymap ]), new Compartment().of(json()), From 815d8f430ee056dcdfaf6a604c4f7250e2f84a04 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 19 Feb 2026 11:48:04 +0000 Subject: [PATCH 3/3] Add tests for undo/redo functionality in InputEditor Co-authored-by: Buckwich <7346215+Buckwich@users.noreply.github.com> --- test/components/Input/InputEditor.spec.js | 108 ++++++++++++++++++++++ 1 file changed, 108 insertions(+) diff --git a/test/components/Input/InputEditor.spec.js b/test/components/Input/InputEditor.spec.js index 811a92d..7ecdf8d 100644 --- a/test/components/Input/InputEditor.spec.js +++ b/test/components/Input/InputEditor.spec.js @@ -312,6 +312,114 @@ describe('InputEditor', function() { }); + + describe('undo/redo', function() { + + it('should support undo after typing', async function() { + + // given + const onChange = sinon.spy(); + const initialValue = '{"foo": "bar"}'; + + const { container, getByRole } = renderWithProps({ + value: initialValue, + onChange + }); + + // when - type some text + await user.click(getByRole('textbox')); + await user.keyboard('{ArrowLeft}{ArrowLeft}"baz": 42, '); + + // then - text was added + await waitFor(() => { + expect(container.textContent).to.include('"baz": 42'); + }); + + // when - undo + await user.keyboard('{Control>}z{/Control}'); + + // then - text was removed + await waitFor(() => { + expect(container.textContent).not.to.include('"baz": 42'); + expect(container.textContent).to.include('"foo": "bar"'); + }); + }); + + + it('should support redo after undo', async function() { + + // given + const onChange = sinon.spy(); + const initialValue = '{"foo": "bar"}'; + + const { container, getByRole } = renderWithProps({ + value: initialValue, + onChange + }); + + // when - type some text + await user.click(getByRole('textbox')); + await user.keyboard('{ArrowLeft}{ArrowLeft}"baz": 42, '); + + await waitFor(() => { + expect(container.textContent).to.include('"baz": 42'); + }); + + // when - undo + await user.keyboard('{Control>}z{/Control}'); + + await waitFor(() => { + expect(container.textContent).not.to.include('"baz": 42'); + }); + + // when - redo + await user.keyboard('{Control>}y{/Control}'); + + // then - text was restored + await waitFor(() => { + expect(container.textContent).to.include('"baz": 42'); + }); + }); + + + it('should support undo after clearing content', async function() { + + // given + const onChange = sinon.spy(); + const initialValue = '{"foo": "bar", "baz": 1337}'; + + const { container, getByRole, rerender } = renderWithProps({ + value: initialValue, + onChange + }); + + // when - clear content by setting value to empty object + rerender( + + ); + + // then - content was cleared + await waitFor(() => { + expect(container.textContent).not.to.include('"foo": "bar"'); + expect(container.textContent).not.to.include('"baz": 1337'); + }); + + // when - focus editor and undo + await user.click(getByRole('textbox')); + await user.keyboard('{Control>}z{/Control}'); + + // then - content was restored + await waitFor(() => { + expect(container.textContent).to.include('"foo": "bar"'); + expect(container.textContent).to.include('"baz": 1337'); + }); + }); + + }); + }); function renderWithProps(props = {}) {