KVv2 json cursor jumps on "enter" (#27569)

* it works...but does it break everything else?

* Update code-mirror.js

* Update code-mirror.js

* return to original

* changelog

* different approach to move onto parse at create and edit. it breaks things, hopefully fixed in next commits

* use onBlur event on codemirrror

* maybe? lets run the tests and find out

* update comments

* wip for conditional to only compare on kvv2

* remove onblur leftovers

* missed two

* clean up

* test coverage

* try catch logical operator instead

* stringify helper and not native json stringify to maintain object shape

* remove comment

* Update json-editor.js

return brackets do not want issues with backports

* Update json-editor.js

* Update json-editor.js

* Test fix

* maybe

* more specific cursor test

* json-editor test cleanup

* Delete ui/testrun1.txt

* Delete ui/testrun2.txt

* remove non json test it doesn't test anything

* update test and comment for how it's testing non-json content

* test fix

* put shape of json blob back:

* send in original without parsing or stringify

* welp friday things
This commit is contained in:
Angel Garbarino 2024-07-10 09:16:21 -06:00 committed by GitHub
parent ed94318ccd
commit 297f8cb3c3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 67 additions and 5 deletions

3
changelog/27569.txt Normal file
View file

@ -0,0 +1,3 @@
```release-note:bug
ui: Fix cursor jump on KVv2 json editor that would occur after pressing ENTER.
```

View file

@ -7,6 +7,7 @@ import { action } from '@ember/object';
import { bind } from '@ember/runloop';
import codemirror from 'codemirror';
import Modifier from 'ember-modifier';
import { stringify } from 'core/helpers/stringify';
import 'codemirror/addon/edit/matchbrackets';
import 'codemirror/addon/selection/active-line';
@ -26,7 +27,19 @@ export default class CodeMirrorModifier extends Modifier {
} else {
// this hook also fires any time there is a change to tracked state
this._editor.setOption('readOnly', namedArgs.readOnly);
if (namedArgs.content && this._editor.getValue() !== namedArgs.content) {
let value = this._editor.getValue();
let content = namedArgs.content;
if (!content) return;
try {
// First parse json to make white space and line breaks consistent between the two items,
// then stringify so they can be compared.
// We use the stringify helper so we do not flatten the json object
value = stringify([JSON.parse(value)], {});
content = stringify([JSON.parse(content)], {});
} catch {
// this catch will occur for non-json content when the mode is not javascript (e.g. ruby).
}
if (value !== content) {
this._editor.setValue(namedArgs.content);
}
}

View file

@ -5,7 +5,16 @@
/* eslint-disable no-useless-escape */
import { module, test } from 'qunit';
import { v4 as uuidv4 } from 'uuid';
import { click, currentURL, fillIn, findAll, setupOnerror, typeIn, visit } from '@ember/test-helpers';
import {
click,
currentURL,
fillIn,
findAll,
setupOnerror,
typeIn,
visit,
triggerKeyEvent,
} from '@ember/test-helpers';
import { setupApplicationTest } from 'vault/tests/helpers';
import authPage from 'vault/tests/pages/auth';
import {
@ -24,6 +33,7 @@ import {
} from 'vault/tests/helpers/kv/policy-generator';
import { clearRecords, writeSecret, writeVersionedSecret } from 'vault/tests/helpers/kv/kv-run-commands';
import { FORM, PAGE } from 'vault/tests/helpers/kv/kv-selectors';
import { GENERAL } from 'vault/tests/helpers/general-selectors';
import codemirror from 'vault/tests/helpers/codemirror';
/**
@ -309,6 +319,19 @@ module('Acceptance | kv-v2 workflow | edge cases', function (hooks) {
assert.false(codemirror().getValue().includes('*'), 'Values are not obscured on edit view');
});
test('on enter the JSON editor cursor goes to the next line', async function (assert) {
// see issue here: https://github.com/hashicorp/vault/issues/27524
const predictedCursorPosition = JSON.stringify({ line: 3, ch: 0, sticky: null });
await visit(`/vault/secrets/${this.backend}/kv/create`);
await fillIn(FORM.inputByAttr('path'), 'json jump');
await click(FORM.toggleJson);
codemirror().setCursor({ line: 2, ch: 1 });
await triggerKeyEvent(GENERAL.codemirrorTextarea, 'keydown', 'Enter');
const actualCursorPosition = JSON.stringify(codemirror().getCursor());
assert.strictEqual(actualCursorPosition, predictedCursorPosition, 'the cursor stayed on the next line');
});
test('viewing advanced secret data versions displays the correct version data', async function (assert) {
assert.expect(2);
const obscuredDataV1 = `{

View file

@ -81,4 +81,6 @@ export const GENERAL = {
cancelButton: '[data-test-cancel]',
saveButton: '[data-test-save]',
maskedInput: (name: string) => `[data-test-textarea="${name}"]`,
codemirror: `[data-test-component="code-mirror-modifier"]`,
codemirrorTextarea: `[data-test-component="code-mirror-modifier"] textarea`,
};

View file

@ -6,7 +6,7 @@
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { create } from 'ember-cli-page-object';
import { render, fillIn, find, waitUntil, click } from '@ember/test-helpers';
import { render, fillIn, find, waitUntil, click, triggerKeyEvent } from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile';
import jsonEditor from '../../pages/components/json-editor';
import sinon from 'sinon';
@ -122,12 +122,33 @@ module('Integration | Component | json-editor', function (hooks) {
assert.dom('.CodeMirror-code').hasText(`{ "test": "********"}`, 'shows data with obscured values');
assert.dom('[data-test-toggle-input="revealValues"]').isNotChecked('reveal values toggle is unchecked');
await click('[data-test-toggle-input="revealValues"]');
assert.dom('.CodeMirror-code').hasText(JSON_BLOB, 'shows data with real values');
// we are hardcoding the hasText comparison instead of using the JSON_BLOB because we no longer match passed content (ex: @value) to the tracked codemirror instance (ex: this._editor.getVale()) if there are line-breaks or whitespace differences.
assert.dom('.CodeMirror-code').hasText(`{ "test": "test" }`, 'shows data with real values');
assert.dom('[data-test-toggle-input="revealValues"]').isChecked('reveal values toggle is checked');
// turn obscure back on to ensure readonly overrides reveal setting
await click('[data-test-toggle-input="revealValues"]');
this.set('readOnly', false);
assert.dom('[data-test-toggle-input="revealValues"]').doesNotExist('reveal values toggle is hidden');
assert.dom('.CodeMirror-code').hasText(JSON_BLOB, 'shows data with real values on edit mode');
assert.dom('.CodeMirror-code').hasText(`{ "test": "test" }`, 'shows data with real values on edit mode');
});
test('code-mirror modifier sets value correctly on non json object', async function (assert) {
// this.value is a tracked property, so anytime it changes the modifier is called. We're testing non-json content by setting the mode to ruby and adding a comment
this.value = null;
await render(hbs`
<JsonEditor
@value={{this.value}}
@mode="ruby"
@valueUpdated={{fn (mut this.value)}}
/>
`);
await fillIn('textarea', '#A comment');
assert.strictEqual(this.value, '#A comment', 'value is set correctly');
await triggerKeyEvent('textarea', 'keydown', 'Enter');
assert.strictEqual(
this.value,
`#A comment\n`,
'even after hitting enter the value is still set correctly'
);
});
});