Skip to content

Commit

Permalink
fix(plasma-react): value prop will no longer display syntax error
Browse files Browse the repository at this point in the history
  • Loading branch information
Mike DiDomizio authored May 13, 2022
1 parent fc09c53 commit ccb15d8
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 36 deletions.
14 changes: 9 additions & 5 deletions packages/react/src/components/editor/CodeEditor.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import loadable from '@loadable/component';
import classNames from 'classnames';
import type {Editor, EditorConfiguration} from 'codemirror';
import {ComponentType, createRef, Component} from 'react';
import {Component, ComponentType, createRef} from 'react';
import type {Controlled} from 'react-codemirror2';
import {connect} from 'react-redux';

import {PlasmaState} from '../../PlasmaState';
import {IDispatch} from '../../utils';
import {CollapsibleSelectors} from '../collapsible/CollapsibleSelectors';
import {CollapsibleSelectors} from '../collapsible';
import {CodeEditorActions} from './CodeEditorActions';
import {CodeMirrorGutters} from './EditorConstants';

Expand Down Expand Up @@ -81,7 +81,7 @@ class CodeEditorDisconnect extends Component<
> {
static defaultProps: Partial<ICodeEditorProps> = {
className: 'mod-border',
value: '{}',
value: '',
};

static defaultOptions = {
Expand Down Expand Up @@ -119,9 +119,13 @@ class CodeEditorDisconnect extends Component<
this.editor.refresh();
this.setState({numberOfRefresh: this.state.numberOfRefresh + 1});
}
if (prevProps.value !== this.props.value && this.editor) {

if (prevProps.value !== this.props.value) {
this.setState({value: this.props.value});
this.editor.getDoc().clearHistory();

if (this.editor) {
this.editor.getDoc().clearHistory();
}
}
}

Expand Down
30 changes: 20 additions & 10 deletions packages/react/src/components/editor/JSONEditor.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import classNames from 'classnames';
import {FunctionComponent, useState, useEffect} from 'react';
import {FunctionComponent, useEffect, useState} from 'react';

import {Svg} from '../svg/Svg';
import {Svg} from '../svg';
import {CodeEditor} from './CodeEditor';
import {CodeMirrorModes, DEFAULT_JSON_ERROR_MESSAGE} from './EditorConstants';
import {JSONEditorUtils} from './JSONEditorUtils';
Expand All @@ -12,11 +12,11 @@ export interface JSONEditorProps {
*/
id?: string;
/**
* @deprecated use defaultValue instead
* The text value of the JSON editor
*/
value?: string;
/**
* The initial value
* @deprecated use value instead
*/
defaultValue?: string;
/**
Expand Down Expand Up @@ -65,8 +65,8 @@ export interface JSONEditorDispatchProps {
export const JSONEditor: FunctionComponent<
JSONEditorProps & Partial<JSONEditorStateProps> & Partial<JSONEditorDispatchProps>
> = ({
value,
defaultValue,
value,
readOnly,
onChange,
errorMessage,
Expand All @@ -77,25 +77,35 @@ export const JSONEditor: FunctionComponent<
onUnmount,
collapsibleId,
}) => {
const [isInError, setIsInError] = useState(!JSONEditorUtils.validateValue(value || defaultValue));
const editorValue = value || defaultValue || '{}';
const [isInError, setIsInError] = useState(!JSONEditorUtils.validateValue(editorValue));

useEffect(() => {
onMount?.();

return onUnmount;
}, []);

const validate = (val: string) => {
const hasError = !JSONEditorUtils.validateValue(val);
setIsInError(hasError);
return hasError;
};

useEffect(() => {
validate(editorValue);
}, [editorValue]);

const handleChange = (json: string) => {
const hasError = !JSONEditorUtils.validateValue(json);
const isValid = validate(json);

setIsInError(hasError);
onChange?.(json, hasError);
return onChange?.(json, isValid);
};

return (
<div className={classNames('form-group', {'input-validation-error': isInError}, containerClasses)}>
<CodeEditor
value={value || defaultValue}
value={editorValue}
onChange={handleChange}
mode={CodeMirrorModes.JSON}
readOnly={readOnly}
Expand Down
42 changes: 27 additions & 15 deletions packages/react/src/components/editor/JSONEditorConnected.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,34 @@
import {connect} from 'react-redux';
import {useDispatch, useSelector} from 'react-redux';
import {useEffect} from 'react';

import {PlasmaState} from '../../PlasmaState';
import {IDispatch} from '../../utils';
import {JSONEditor, JSONEditorDispatchProps, JSONEditorProps, JSONEditorStateProps} from './JSONEditor';
import {JSONEditor, JSONEditorProps} from './JSONEditor';
import {JSONEditorActions} from './JSONEditorActions';
import {JSONEditorSelectors} from './JSONEditorSelectors';

const mapStateToProps = (state: PlasmaState, ownProps: JSONEditorProps): JSONEditorStateProps => ({
value: JSONEditorSelectors.getValue(state, ownProps.id),
});
interface JSONEditorConnectedProps {
/**
* initial value of the component
*/
defaultValue?: string;
}

const mapDispatchToProps = (dispatch: IDispatch, ownProps: JSONEditorProps): JSONEditorDispatchProps => ({
onMount: () => dispatch(JSONEditorActions.addJSONEditor(ownProps.id, ownProps.defaultValue ?? ownProps.value)),
onUnmount: () => dispatch(JSONEditorActions.removeJSONEditor(ownProps.id)),
onChange: (value: string, inError: boolean) => {
dispatch(JSONEditorActions.updateJSONEditorValue(ownProps.id, value));
ownProps.onChange?.(value, inError);
},
});
export const JSONEditorConnected = (props: JSONEditorProps & JSONEditorConnectedProps) => {
const dispatch: IDispatch = useDispatch();
const value = useSelector((state) => JSONEditorSelectors.getValue(state, props.id));

export const JSONEditorConnected = connect(mapStateToProps, mapDispatchToProps)(JSONEditor);
useEffect(() => {
dispatch(JSONEditorActions.addJSONEditor(props.id, props.defaultValue || props.value));

return () => {
dispatch(JSONEditorActions.removeJSONEditor(props.id));
};
}, []);

const onChange = (changedValue: string, inError: boolean) => {
dispatch(JSONEditorActions.updateJSONEditorValue(props.id, changedValue));
props.onChange?.(changedValue, inError);
};

return <JSONEditor {...props} value={value} onChange={onChange} />;
};
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import {shallow, ShallowWrapper} from 'enzyme';
import * as _ from 'underscore';

import {CodeEditor} from '../CodeEditor';
import {CodeMirrorModes} from '../EditorConstants';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,28 @@ describe('<JSONEditorConnected />', () => {
}).not.toThrow();
});

it('will display brackets if no value/defaultValue is provided as it is a JSON editor', async () => {
const {container} = render(<JSONEditorConnected id="💙" />);

await waitFor(() => expect(screen.getByRole('textbox')).toBeVisible());

// eslint-disable-next-line testing-library/no-node-access,testing-library/no-container
const line = container.querySelector('.CodeMirror-line [role="presentation"]') as HTMLElement;

// the codemirror divide the text in multiple elements, by using textContent we "strip" the html
const matcher = (_: string, element: HTMLElement) => element?.textContent === '{}';

expect(within(line).getByText(matcher)).toBeVisible();
});

it('will not display error when rendering with (deprecated) value prop', async() => {
render(<JSONEditorConnected id="💙" value={'{}'} />);

await waitFor(() => expect(screen.getByRole('textbox')).toBeVisible());

expect(screen.queryByText(/JSON configuration is syntactically invalid/i)).not.toBeInTheDocument();
});

it('should not throw when content changes', async () => {
render(<JSONEditorConnected id="💙" defaultValue={'{}'} />);

Expand Down Expand Up @@ -49,10 +71,10 @@ describe('<JSONEditorConnected />', () => {
userEvent.type(screen.getByRole('textbox'), expectedValue);

expect(onChangeSpy).toHaveBeenCalledTimes(5);
expect(onChangeSpy).toHaveBeenCalledWith('h', true);
expect(onChangeSpy).toHaveBeenCalledWith('he', true);
expect(onChangeSpy).toHaveBeenCalledWith('hel', true);
expect(onChangeSpy).toHaveBeenCalledWith('hell', true);
expect(onChangeSpy).toHaveBeenCalledWith('hello', true);
expect(onChangeSpy).toHaveBeenCalledWith('{}h', true);
expect(onChangeSpy).toHaveBeenCalledWith('{}he', true);
expect(onChangeSpy).toHaveBeenCalledWith('{}hel', true);
expect(onChangeSpy).toHaveBeenCalledWith('{}hell', true);
expect(onChangeSpy).toHaveBeenCalledWith('{}hello', true);
});
});

0 comments on commit ccb15d8

Please sign in to comment.