Repository: styleguidist/react-styleguidist (ts)

Issue:
Pass evalInContext to Slot
<!-- Consider opening a pull request instead: it’s a more productive way to discuss new features -->

**The problem**
In order to support a proposed "show HTML" slot plugin, the `Slot` component will need access to `evalInContext` which is passed to the `Playground` so that it can use the `CodeEvaluator` proposed in #976.

**Proposed solution**
Update `Playground` to pass `evalInContext` to `Slot`

Pull request description:
Playground passes evalInContext to Slot
Fixes #977

`Playground` passes `evalInContext` to `Slot`

Patch:
diff --git a/cypress.json b/cypress.json
index fdff325b..ffc2840d 100644
--- a/cypress.json
+++ b/cypress.json
@@ -5,5 +5,6 @@
   "pluginsFile": "test/cypress/plugins/index.js",
   "screenshotsFolder": "test/cypress/screenshots",
   "videosFolder": "test/cypress/videos",
-  "supportFile": "test/cypress/support/index.js"
+  "supportFile": "test/cypress/support/index.js",
+  "videoRecording": false
 }
diff --git a/src/rsg-components/Editor/Editor.js b/src/rsg-components/Editor/Editor.js
index b285c536..2421ad07 100644
--- a/src/rsg-components/Editor/Editor.js
+++ b/src/rsg-components/Editor/Editor.js
@@ -16,7 +16,8 @@ const UPDATE_DELAY = 10;
 export default class Editor extends Component {
 	static propTypes = {
 		code: PropTypes.string.isRequired,
-		onChange: PropTypes.func.isRequired,
+		onChange: PropTypes.func,
+		editorConfig: PropTypes.object,
 	};
 	static contextTypes = {
 		config: PropTypes.object.isRequired,
@@ -27,17 +28,32 @@ export default class Editor extends Component {
 		this.handleChange = debounce(this.handleChange.bind(this), UPDATE_DELAY);
 	}
 
-	shouldComponentUpdate() {
-		return false;
+	shouldComponentUpdate(nextProps) {
+		return !!(this.getEditorConfig(nextProps).readOnly && nextProps.code !== this.props.code);
+	}
+
+	getEditorConfig(props) {
+		return {
+			...this.context.config.editorConfig,
+			...props.editorConfig,
+		};
 	}
 
 	handleChange(editor, metadata, newCode) {
-		this.props.onChange(newCode);
+		const { onChange } = this.props;
+		if (onChange) {
+			onChange(newCode);
+		}
 	}
 
 	render() {
 		const { code } = this.props;
-		const { editorConfig } = this.context.config;
-		return <CodeMirror value={code} onChange={this.handleChange} options={editorConfig} />;
+		return (
+			<CodeMirror
+				value={code}
+				onChange={this.handleChange}
+				options={this.getEditorConfig(this.props)}
+			/>
+		);
 	}
 }
diff --git a/src/rsg-components/Editor/Editor.spec.js b/src/rsg-components/Editor/Editor.spec.js
index 2db90255..fdf198e4 100644
--- a/src/rsg-components/Editor/Editor.spec.js
+++ b/src/rsg-components/Editor/Editor.spec.js
@@ -11,6 +11,7 @@ const options = {
 			showCode: false,
 			highlightTheme: 'base16-light',
 			editorConfig: {
+				lineWrapping: true,
 				mode: 'js',
 			},
 		},
@@ -30,7 +31,10 @@ describe('EditorLoaderRenderer', () => {
 
 describe('Editor', () => {
 	it('should renderer and editor', () => {
-		const actual = shallow(<Editor code={code} onChange={() => {}} />, options);
+		const actual = shallow(
+			<Editor code={code} editorConfig={{ readOnly: true, mode: 'text/html' }} />,
+			options
+		);
 
 		expect(actual).toMatchSnapshot();
 	});
@@ -53,4 +57,20 @@ describe('Editor', () => {
 			done();
 		}, 13);
 	});
+
+	it('should not update if not read only', () => {
+		const actual = shallow(<Editor code={code} />, options);
+
+		const shouldUpdate = actual.instance().shouldComponentUpdate({ code: newCode });
+		expect(shouldUpdate).toBe(false);
+	});
+
+	it('should update if read only and code has changed', () => {
+		const actual = shallow(<Editor code={code} editorConfig={{ readOnly: true }} />, options);
+
+		const shouldUpdate = actual
+			.instance()
+			.shouldComponentUpdate({ code: newCode, editorConfig: { readOnly: true } });
+		expect(shouldUpdate).toBe(true);
+	});
 });
diff --git a/src/rsg-components/Editor/__snapshots__/Editor.spec.js.snap b/src/rsg-components/Editor/__snapshots__/Editor.spec.js.snap
index 9581eb34..714818fa 100644
--- a/src/rsg-components/Editor/__snapshots__/Editor.spec.js.snap
+++ b/src/rsg-components/Editor/__snapshots__/Editor.spec.js.snap
@@ -5,7 +5,9 @@ exports[`Editor should renderer and editor 1`] = `
   onChange={[Function]}
   options={
     Object {
-      "mode": "js",
+      "lineWrapping": true,
+      "mode": "text/html",
+      "readOnly": true,
     }
   }
   value=
diff --git a/src/rsg-components/Playground/Playground.js b/src/rsg-components/Playground/Playground.js
index 6982ee3b..f31722a3 100644
--- a/src/rsg-components/Playground/Playground.js
+++ b/src/rsg-components/Playground/Playground.js
@@ -86,7 +86,7 @@ export default class Playground extends Component {
 						name="exampleTabs"
 						active={activeTab}
 						onlyActive
-						props={{ code, onChange: this.handleChange }}
+						props={{ code, onChange: this.handleChange, evalInContext }}
 					/>
 				}
 				toolbar={
diff --git a/src/rsg-components/Playground/__snapshots__/Playground.spec.js.snap b/src/rsg-components/Playground/__snapshots__/Playground.spec.js.snap
index e35f0f90..a5cc2a5a 100644
--- a/src/rsg-components/Playground/__snapshots__/Playground.spec.js.snap
+++ b/src/rsg-components/Playground/__snapshots__/Playground.spec.js.snap
@@ -65,6 +65,7 @@ exports[`should render component renderer 1`] = `
   OK
 </button>
 ,
+          "evalInContext": [Function],
           "onChange": [Function],
         }
       }


Test patch:
diff --git a/test/cypress/integration/component_spec.js b/test/cypress/integration/component_spec.js
index 71c1402c..db3f7296 100644
--- a/test/cypress/integration/component_spec.js
+++ b/test/cypress/integration/component_spec.js
@@ -77,19 +77,21 @@ describe('Single component', () => {
 		});
 
 		it('changes the render after code change', () => {
+			const codeToDelete = '</Button>';
 			cy
 				.get('@container')
 				.find('.CodeMirror textarea')
-				// CodeMirror actually listens to keystrokes on an empty textarea to update the div with the code
-				// so we have to hack our way around it with a bunch of backspacing, since there's no way to place the cursor
-				.type(
-					`{backspace}{backspace}{backspace}{backspace}{backspace}{backspace}{backspace}{backspace}{backspace} Harder</Button>`,
-					{
-						force: true,
-					}
-				);
-			// need to wait for CodeMirror to update
+				// CodeMirror actually listens to keystrokes on an empty textarea
+				// to update the div with the code, so we have to hack our way
+				// around it with a bunch of backspacing, since there's no way
+				// to place the cursor
+				.type(`${'{backspace}'.repeat(codeToDelete.length)} Harder${codeToDelete}`, {
+					force: true,
+				});
+
+			// Wait for CodeMirror to update
 			cy.wait(500);
+
 			cy
 				.get('@preview')
 				.find('button')