From 4471ba82711319b5e0f1db134451d0726aa2308e Mon Sep 17 00:00:00 2001 From: Vitali Pinchuk <146737590+vitPinchuk@users.noreply.github.com> Date: Mon, 9 Dec 2024 21:35:34 +0300 Subject: [PATCH] Update new lines in Text Area and Code Editor (#563) * convert value for CODE and TEXTAREA * ci tests added * update code editor * package lock * Update CHANGELOG.md --------- Co-authored-by: Mikhail Volkov --- CHANGELOG.md | 1 + package-lock.json | 12 +-- package.json | 2 +- .../CustomCodeEditor.test.tsx | 25 +++-- .../components/CodeElement/CodeElement.tsx | 4 +- src/utils/form-element.test.ts | 96 +++++++++++++++++++ src/utils/form-element.ts | 8 +- 7 files changed, 130 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 71f54200..9899590c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Updated change value behavior for custom option (#562) - Added form-element of type color (#561) +- Updated new lines in Text Area and Code Editor (#563) ## 4.9.0 (2024-11-16) diff --git a/package-lock.json b/package-lock.json index 4b45a2ca..7315a405 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "business-forms", - "version": "4.9.0", + "version": "5.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "business-forms", - "version": "4.9.0", + "version": "5.0.0", "license": "Apache-2.0", "dependencies": { "@emotion/css": "^11.13.0", @@ -15,7 +15,7 @@ "@grafana/scenes": "^5.20.4", "@grafana/ui": "^11.3.0", "@hello-pangea/dnd": "^17.0.0", - "@volkovlabs/components": "^3.5.0", + "@volkovlabs/components": "^3.6.0", "react": "^18.3.1", "react-dom": "^18.3.1", "semver": "^7.6.3", @@ -4493,9 +4493,9 @@ "peer": true }, "node_modules/@volkovlabs/components": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/@volkovlabs/components/-/components-3.5.0.tgz", - "integrity": "sha512-WFHELd5hlBPmSb3vzOPWUAMYeax2GQnv9DjkXXtkBTgXex1RSfz0YMAR49ZuVd4klm9NPSoFUh0XbLqMgvzsDA==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@volkovlabs/components/-/components-3.6.0.tgz", + "integrity": "sha512-vZYz3bGJ78GF0VhHYJ0uUO8O927oUWEELkkJYcWTSrED8FaiiEnm7kWy2KDcZCCv6MU6YRYfy/ol8mXDGJyUXQ==", "dependencies": { "@emotion/css": "^11.11.2", "@grafana/data": "^11.1.0", diff --git a/package.json b/package.json index d407b784..8f7bb9d6 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "@grafana/scenes": "^5.20.4", "@grafana/ui": "^11.3.0", "@hello-pangea/dnd": "^17.0.0", - "@volkovlabs/components": "^3.5.0", + "@volkovlabs/components": "^3.6.0", "react": "^18.3.1", "react-dom": "^18.3.1", "semver": "^7.6.3", diff --git a/src/components/CustomCodeEditor/CustomCodeEditor.test.tsx b/src/components/CustomCodeEditor/CustomCodeEditor.test.tsx index f91651af..8796ee97 100644 --- a/src/components/CustomCodeEditor/CustomCodeEditor.test.tsx +++ b/src/components/CustomCodeEditor/CustomCodeEditor.test.tsx @@ -1,6 +1,7 @@ import { getTemplateSrv } from '@grafana/runtime'; -import { CodeEditor, CodeEditorSuggestionItemKind } from '@grafana/ui'; +import { CodeEditorSuggestionItemKind } from '@grafana/ui'; import { render, screen } from '@testing-library/react'; +import { AutosizeCodeEditor } from '@volkovlabs/components'; import React, { useEffect } from 'react'; import { CODE_EDITOR_SUGGESTIONS } from '@/constants'; @@ -16,6 +17,14 @@ jest.mock('@grafana/runtime', () => ({ getTemplateSrv: jest.fn(), })); +/** + * Mock @volkovlabs/components + */ +jest.mock('@volkovlabs/components', () => ({ + ...jest.requireActual('@volkovlabs/components'), + AutosizeCodeEditor: jest.fn().mockImplementation(() => null), +})); + /** * Mock timers */ @@ -51,7 +60,7 @@ describe('Custom Code Editor', () => { it('Should show mini map if value more than 100 symbols', () => { render(getComponent({ value: new Array(102).join('1') })); - expect(CodeEditor).toHaveBeenCalledWith( + expect(AutosizeCodeEditor).toHaveBeenCalledWith( expect.objectContaining({ showMiniMap: true, }), @@ -70,7 +79,7 @@ describe('Custom Code Editor', () => { })), }; - jest.mocked(CodeEditor).mockImplementationOnce(({ onEditorDidMount }: any) => { + jest.mocked(AutosizeCodeEditor).mockImplementationOnce(({ onEditorDidMount }: any) => { useEffect(() => { onEditorDidMount(editor); }, [onEditorDidMount]); @@ -86,7 +95,7 @@ describe('Custom Code Editor', () => { /** * Check if formatDocument is run */ - expect(CodeEditor).toHaveBeenCalledWith( + expect(AutosizeCodeEditor).toHaveBeenCalledWith( expect.objectContaining({ monacoOptions: { formatOnPaste: true, @@ -107,7 +116,7 @@ describe('Custom Code Editor', () => { const value = 'some value'; const onChange = jest.fn(); - jest.mocked(CodeEditor).mockImplementationOnce(({ onBlur }: any) => { + jest.mocked(AutosizeCodeEditor).mockImplementationOnce(({ onBlur }: any) => { onBlur(value); return null; }); @@ -134,7 +143,7 @@ describe('Custom Code Editor', () => { const value = 'some value'; const onChange = jest.fn(); - jest.mocked(CodeEditor).mockImplementationOnce(({ onSave }: any) => { + jest.mocked(AutosizeCodeEditor).mockImplementationOnce(({ onSave }: any) => { onSave(value); return null; }); @@ -163,7 +172,7 @@ describe('Custom Code Editor', () => { const variableWithoutDescription = { name: 'var2', description: '', label: 'Var 2' }; const variables = [variableWithDescription, variableWithoutDescription]; - jest.mocked(CodeEditor).mockImplementationOnce(({ getSuggestions }: any) => { + jest.mocked(AutosizeCodeEditor).mockImplementationOnce(({ getSuggestions }: any) => { suggestionsResult = getSuggestions(); return null; }); @@ -218,7 +227,7 @@ describe('Custom Code Editor', () => { const variableWithoutDescription = { name: 'var2', description: '', label: 'Var 2' }; const variables = [variableWithDescription, variableWithoutDescription]; - jest.mocked(CodeEditor).mockImplementationOnce(({ getSuggestions }: any) => { + jest.mocked(AutosizeCodeEditor).mockImplementationOnce(({ getSuggestions }: any) => { suggestionsResult = getSuggestions(); return null; }); diff --git a/src/components/FormElement/components/CodeElement/CodeElement.tsx b/src/components/FormElement/components/CodeElement/CodeElement.tsx index 73dd3d50..a9010127 100644 --- a/src/components/FormElement/components/CodeElement/CodeElement.tsx +++ b/src/components/FormElement/components/CodeElement/CodeElement.tsx @@ -53,13 +53,13 @@ export const CodeElement: React.FC = ({ element, onChange }) => { language={element.language || CodeLanguage.JAVASCRIPT} showLineNumbers={true} showMiniMap={(element.value?.length || 0) > 100} - value={element.value?.replaceAll('\\n', '\n') || ''} + value={element.value || ''} height={element.height} width={applyWidth(element.width)} onBlur={(code) => { onChange({ ...element, - value: code.replaceAll('\n', '\\n'), + value: code, }); }} monacoOptions={monacoOptions} diff --git a/src/utils/form-element.test.ts b/src/utils/form-element.test.ts index 9acfdc4a..e7a659a6 100644 --- a/src/utils/form-element.test.ts +++ b/src/utils/form-element.test.ts @@ -321,6 +321,102 @@ describe('Utils', () => { }, ], }, + { + name: 'Should convert value for code element', + element: { + type: FormElementType.CODE, + }, + testCases: [ + { + original: 'line1', + expected: 'line1', + }, + ], + }, + { + name: 'Should convert value for code element and replace new lines', + element: { + type: FormElementType.CODE, + }, + testCases: [ + { + original: 'line1\n', + expected: 'line1\\n', + }, + ], + }, + { + name: 'Should convert value for code element if none-string', + element: { + type: FormElementType.CODE, + }, + testCases: [ + { + original: 15, + expected: '15', + }, + ], + }, + { + name: 'Should convert value for code element if no value', + element: { + type: FormElementType.CODE, + }, + testCases: [ + { + original: '', + expected: '', + }, + ], + }, + { + name: 'Should convert value for code element if value is undefined', + element: { + type: FormElementType.CODE, + }, + testCases: [ + { + original: undefined, + expected: '', + }, + ], + }, + { + name: 'Should convert value for textarea element', + element: { + type: FormElementType.TEXTAREA, + }, + testCases: [ + { + original: 'line1', + expected: 'line1', + }, + ], + }, + { + name: 'Should convert value for textarea element and replace new lines', + element: { + type: FormElementType.TEXTAREA, + }, + testCases: [ + { + original: 'line1\n', + expected: 'line1\\n', + }, + ], + }, + { + name: 'Should convert value for textarea element if none-string', + element: { + type: FormElementType.TEXTAREA, + }, + testCases: [ + { + original: 15, + expected: '15', + }, + ], + }, ])('$name', ({ element, testCases }) => { testCases.forEach(({ original, expected }) => { expect(convertToElementValue(element as never, original)).toEqual({ diff --git a/src/utils/form-element.ts b/src/utils/form-element.ts index 52262ccd..19828de9 100644 --- a/src/utils/form-element.ts +++ b/src/utils/form-element.ts @@ -477,9 +477,15 @@ export const convertToElementValue = ( value: unknown ): FormElementByType => { switch (element.type) { + case FormElementType.CODE: + case FormElementType.TEXTAREA: { + return { + ...element, + value: typeof value === 'string' ? value.replaceAll('\n', '\\n') : (value?.toString() ?? ''), + }; + } case FormElementType.STRING: case FormElementType.DISABLED_TEXTAREA: - case FormElementType.CODE: case FormElementType.PASSWORD: case FormElementType.SECRET: case FormElementType.COLOR_PICKER: