diff --git a/README.md b/README.md index 21fd4496..b781ea3a 100644 --- a/README.md +++ b/README.md @@ -7,15 +7,16 @@ [![CDNJS](https://img.shields.io/cdnjs/v/react-ace.svg)](https://cdnjs.com/libraries/react-ace) [![Coverage Status](https://coveralls.io/repos/github/securingsincity/react-ace/badge.svg?branch=master)](https://coveralls.io/github/securingsincity/react-ace?branch=master) -A react component for Ace / Brace +A set of react components for Ace / Brace -[DEMO](http://securingsincity.github.io/react-ace/) +[DEMO of React Ace](http://securingsincity.github.io/react-ace/) +[DEMO of React Ace Split Editor](http://securingsincity.github.io/react-ace/split.html) ## Install `npm install react-ace` -## Usage +## Basic Usage ```javascript import React from 'react'; @@ -45,94 +46,14 @@ render( ## Examples -* Checkout `example` directory for a working example using webpack. -* [create-react-app](https://github.com/securingsincity/react-ace-create-react-app-example) -* [preact](https://github.com/securingsincity/react-ace-preact-example) -* [webpack](https://github.com/securingsincity/react-ace-webpack-example) - - -## Available Props - -|Prop|Default|Description| -|-----|------|----------| -|name| 'brace-editor'| Unique Id to be used for the editor| -|mode| ''| Language for parsing and code highlighting| -|theme| ''| theme to use| -|height| '500px'| CSS value for height| -|width| '500px'| CSS value for width| -|className| | custom className| -|fontSize| 12| pixel value for font-size| -|showGutter| true| boolean| -|showPrintMargin| true| boolean| -|highlightActiveLine| true| boolean| -|focus| false| boolean| -|cursorStart| 1| number| -|wrapEnabled| false| Wrapping lines| -|readOnly| false| boolean| -|minLines| | Minimum number of lines to be displayed| -|maxLines| | Maximum number of lines to be displayed| -|enableBasicAutocompletion| false| Enable basic autocompletion| -|enableLiveAutocompletion| false| Enable live autocompletion| -|tabSize| 4| tabSize number| -|value | ''| String value you want to populate in the code highlighter| -|defaultValue | ''| Default value of the editor| -|onLoad| | Function onLoad| -|onBeforeLoad| | function that trigger before editor setup| -|onChange| | function that occurs on document change it has 2 arguments the value and the event. see the example above| -|onCopy| | function that trigger by editor `copy` event, and pass text as argument| -|onPaste| | function that trigger by editor `paste` event, and pass text as argument| -|onSelectionChange| | function that trigger by editor `selectionChange` event, and passes a [Selection](https://ace.c9.io/#nav=api&api=selection) as it's first argument and the event as the second| -|onFocus| | function that trigger by editor `focus` event| -|onBlur| | function that trigger by editor `blur` event| -|onScroll| | function that trigger by editor `scroll` event| -|editorProps| | Object of properties to apply directly to the Ace editor instance| -|setOptions| | Object of [options](https://github.com/ajaxorg/ace/wiki/Configuring-Ace) to apply directly to the Ace editor instance| -|keyboardHandler| | String corresponding to the keybinding mode to set (such as vim)| -|commands| | Array of new commands to add to the editor -|annotations| | Array of annotations to show in the editor i.e. `[{ row: 0, column: 2, type: 'error', text: 'Some error.'}]`, displayed in the gutter| -|markers| | Array of [markers](https://ace.c9.io/api/edit_session.html#EditSession.addMarker) to show in the editor, i.e. `[{ startRow: 0, startCol: 2, endRow: 1, endCol: 20, className: 'error-marker', type: 'background' }]`| -|style| | Object with camelCased properties | - -## Modes, Themes, and Keyboard Handlers - -All modes, themes, and keyboard handlers should be required through ```brace``` directly. Browserify will grab these modes / themes / keyboard handlers through ```brace``` and will be available at run time. See the example above. This prevents bloating the compiled javascript with extra modes and themes for your application. - -### Example Modes - -* javascript -* java -* python -* xml -* ruby -* sass -* markdown -* mysql -* json -* html -* handlebars -* golang -* csharp -* coffee -* css - -### Example Themes - -* monokai -* github -* tomorrow -* kuroir -* twilight -* xcode -* textmate -* solarized dark -* solarized light -* terminal - -### Example Keyboard Handlers - -* vim -* emacs +Checkout the `example` directory for a working example using webpack. +## Documentation + +[Ace Editor](https://github.com/securingsincity/react-ace/blob/master/docs/Ace.md) +[Split View Editor](https://github.com/securingsincity/react-ace/blob/master/docs/Split.md) +[How to add modes, themes and keyboard handlers](https://github.com/securingsincity/react-ace/blob/master/docs/Modes.md) +[Frequently Asked Questions](https://github.com/securingsincity/react-ace/blob/master/docs/FAQ.md) ## Backers diff --git a/docs/Ace.md b/docs/Ace.md new file mode 100644 index 00000000..4de735fa --- /dev/null +++ b/docs/Ace.md @@ -0,0 +1,45 @@ +# Ace Editor + +This is the main component of React-Ace. It creates an instance of the Ace Editor. + +## Available Props + +|Prop|Default|Type|Description| +|-----|------|-----|-----| +|name| 'brace-editor'| String |Unique Id to be used for the editor| +|mode| ''| String |Language for parsing and code highlighting| +|theme| ''| String |theme to use| +|value | ''| String | value you want to populate in the code highlighter| +|defaultValue | ''| String |Default value of the editor| +|height| '500px'| String |CSS value for height| +|width| '500px'| String |CSS value for width| +|className| | String |custom className| +|fontSize| 12| Number |pixel value for font-size| +|showGutter| true| Boolean | show gutter | +|showPrintMargin| true| Boolean| show print margin | +|highlightActiveLine| true| Boolean| highlight active line| +|focus| false| Boolean| whether to focus +|cursorStart| 1| Number| the location of the cursor +|wrapEnabled| false| Boolean | Wrapping lines| +|readOnly| false| Boolean| make the editor read only | +|minLines| | Number |Minimum number of lines to be displayed| +|maxLines| | Number |Maximum number of lines to be displayed| +|enableBasicAutocompletion| false| Boolean | Enable basic autocompletion| +|enableLiveAutocompletion| false| Boolean | Enable live autocompletion| +|tabSize| 4| Number| tabSize| +|onLoad| | Function | called on editor load. The first argument is the instance of the editor | +|onBeforeLoad| | Function | called before editor load. the first argument is an instance of `ace`| +|onChange| | Function | occurs on document change it has 2 arguments the value and the event.| +|onCopy| | Function | triggered by editor `copy` event, and passes text as argument| +|onPaste| | Function | Triggered by editor `paste` event, and passes text as argument| +|onSelectionChange| | Function | triggered by editor `selectionChange` event, and passes a [Selection](https://ace.c9.io/#nav=api&api=selection) as it's first argument and the event as the second| +|onFocus| | Function | triggered by editor `focus` event| +|onBlur| | Function | triggered by editor `blur` event| +|onScroll| | Function | triggered by editor `scroll` event| +|editorProps| | Object | properties to apply directly to the Ace editor instance| +|setOptions| | Object | [options](https://github.com/ajaxorg/ace/wiki/Configuring-Ace) to apply directly to the Ace editor instance| +|keyboardHandler| | String | corresponding to the keybinding mode to set (such as vim or emacs)| +|commands| | Array | new commands to add to the editor +|annotations| | Array | annotations to show in the editor i.e. `[{ row: 0, column: 2, type: 'error', text: 'Some error.'}]`, displayed in the gutter| +|markers| | Array | [markers](https://ace.c9.io/api/edit_session.html#EditSession.addMarker) to show in the editor, i.e. `[{ startRow: 0, startCol: 2, endRow: 1, endCol: 20, className: 'error-marker', type: 'background' }]`| +|style| | Object | camelCased properties | diff --git a/docs/FAQ.md b/docs/FAQ.md new file mode 100644 index 00000000..d14c1526 --- /dev/null +++ b/docs/FAQ.md @@ -0,0 +1,159 @@ +# Frequently Asked Questions + +## How do I use it with `preact`? `webpack`? `create-react-app`? + +Check out the example applications + +* [create-react-app](https://github.com/securingsincity/react-ace-create-react-app-example) +* [preact](https://github.com/securingsincity/react-ace-preact-example) +* [webpack](https://github.com/securingsincity/react-ace-webpack-example) + + +## How do call methods on the editor? How do I call Undo or Redo? + +`ReactAce` has an editor property, which is the wrapped editor. You can use refs to get to the component, and then you should be able to use the editor on the component to run the function you need: + +```javascript +const reactAceComponent = parent.refs.reactAceComponent; +const editor = reactAceComponent.editor +editor.find(searchRegex, { + backwards: false, + wrap: true, + caseSensitive: false, + wholeWord: false, + regExp: true, +}); +``` + +Similarly, if you want to redo or undo, you can reference the editor from the refs + +```jsx + + +``` + +## How do I set editor options like setting block scrolling to infinity? + +```javascript + +``` + +## How do I add language snippets? + +You can import the snippets and mode directly through `brace` along with the language_tools. Here is an example below + + +```javascript +import React from 'react'; +import { render } from 'react-dom'; +import brace from 'brace'; +import AceEditor from 'react-ace'; + +import 'brace/mode/python'; +import 'brace/snippets/python'; +import 'brace/ext/language_tools'; +import 'brace/theme/github'; + +function onChange(newValue) { + console.log('change',newValue); +} + +// Render editor +render( + , + document.getElementById('example') +); +``` + +## How do I get selected text `onSelectionChange`? + +How you extract the text from the editor is based on how to call methods on the editor. + +Your `onSelectionChange` should look like this: + +```javascript +onSelectionChange(selection) { + const content = this.refs.aceEditor.editor.session.getTextRange(selection.getRange()); + // use content +} +``` + +## How do I add markers? +```javascript + const markers = [{ + startRow: 3, + type: 'text', + className: 'test-marker' + }]; + const wrapper = (); +``` + +## How do I add annotations? +```javascript + const annotations = [{ + row: 3, // must be 0 based + column: 4, // must be 0 based + text: 'error.message', // text to show in tooltip + type: 'error' + }] + const editor = ( + + ) +``` + +## How do I add the search box? +Add the following line + +`import 'brace/ext/searchbox';` + +before introducing the component and it will add the search box. + +## How do I add a custom mode? + +1. Create my custom mode class (pure ES6 code) +2. Initialize the component with an existing mode name (such as "sql") +3. Use the `componentDidMount` function and call `session.setMode` with an instance of my custom mode. + +My custom mode is: +```javascript +export default class CustomSqlMode extends ace.acequire('ace/mode/text').Mode { + constructor(){ + super(); + // Your code goes here + } +} +``` + +And my react-ace code looks like: +```javascript +render() { + return
+ +
; +} + +componentDidMount() { + const customMode = new CustomSqlMode(); + this.refs.aceEditor.editor.getSession().setMode(customMode); +} +``` + diff --git a/docs/Modes.md b/docs/Modes.md new file mode 100644 index 00000000..9c2c3950 --- /dev/null +++ b/docs/Modes.md @@ -0,0 +1,39 @@ +# Modes, Themes, and Keyboard Handlers + +All modes, themes, and keyboard handlers should be required through ```brace``` directly. Browserify will grab these modes / themes / keyboard handlers through ```brace``` and will be available at run time. See the example above. This prevents bloating the compiled javascript with extra modes and themes for your application. + +### Example Modes + +* javascript +* java +* python +* xml +* ruby +* sass +* markdown +* mysql +* json +* html +* handlebars +* golang +* csharp +* coffee +* css + +### Example Themes + +* monokai +* github +* tomorrow +* kuroir +* twilight +* xcode +* textmate +* solarized dark +* solarized light +* terminal + +### Example Keyboard Handlers + +* vim +* emacs \ No newline at end of file diff --git a/docs/Split.md b/docs/Split.md new file mode 100644 index 00000000..061a83cf --- /dev/null +++ b/docs/Split.md @@ -0,0 +1,45 @@ +# Split Editor + +This allows for a split editor which can create multiple linked instances of the Ace editor. Each instance shares a theme and other properties while having their own value. + +## Available Props + +|Prop|Default|Type|Description| +|-----|------|-----|-----| +|name| 'brace-editor'| String |Unique Id to be used for the editor| +|mode| ''| String |Language for parsing and code highlighting| +|theme| ''| String |theme to use| +|value | ''| Array of Strings | value you want to populate in each code editor| +|defaultValue | ''| Array of Strings |Default value for each editor| +|height| '500px'| String |CSS value for height| +|width| '500px'| String |CSS value for width| +|className| | String |custom className| +|fontSize| 12| Number |pixel value for font-size| +|showGutter| true| Boolean | show gutter | +|showPrintMargin| true| Boolean| show print margin | +|highlightActiveLine| true| Boolean| highlight active line| +|focus| false| Boolean| whether to focus +|cursorStart| 1| Number| the location of the cursor +|wrapEnabled| false| Boolean | Wrapping lines| +|readOnly| false| Boolean| make the editor read only | +|minLines| | Number |Minimum number of lines to be displayed| +|maxLines| | Number |Maximum number of lines to be displayed| +|enableBasicAutocompletion| false| Boolean | Enable basic autocompletion| +|enableLiveAutocompletion| false| Boolean | Enable live autocompletion| +|tabSize| 4| Number| tabSize| +|onLoad| | Function | called on editor load. The first argument is the instance of the editor | +|onBeforeLoad| | Function | called before editor load. the first argument is an instance of `ace`| +|onChange| | Function | occurs on document change it has 2 arguments the value of each editor and the event.| +|onCopy| | Function | triggered by editor `copy` event, and passes text as argument| +|onPaste| | Function | Triggered by editor `paste` event, and passes text as argument| +|onSelectionChange| | Function | triggered by editor `selectionChange` event, and passes a [Selection](https://ace.c9.io/#nav=api&api=selection) as it's first argument and the event as the second| +|onFocus| | Function | triggered by editor `focus` event| +|onBlur| | Function | triggered by editor `blur` event| +|onScroll| | Function | triggered by editor `scroll` event| +|editorProps| | Object | properties to apply directly to the Ace editor instance| +|setOptions| | Object | [options](https://github.com/ajaxorg/ace/wiki/Configuring-Ace) to apply directly to the Ace editor instance| +|keyboardHandler| | String | corresponding to the keybinding mode to set (such as vim or emacs)| +|commands| | Array | new commands to add to the editor +|annotations| | Array of Arrays | annotations to show in the editor i.e. `[{ row: 0, column: 2, type: 'error', text: 'Some error.'}]`, displayed in the gutter| +|markers| | Array of Arrays | [markers](https://ace.c9.io/api/edit_session.html#EditSession.addMarker) to show in the editor, i.e. `[{ startRow: 0, startCol: 2, endRow: 1, endCol: 20, className: 'error-marker', type: 'background' }]`| +|style| | Object | camelCased properties | diff --git a/example/index.html b/example/index.html index fb3830ee..2259f389 100644 --- a/example/index.html +++ b/example/index.html @@ -12,6 +12,6 @@

React-Ace

- + diff --git a/example/index.js b/example/index.js index b1e9250a..dfa0aa53 100644 --- a/example/index.js +++ b/example/index.js @@ -1,12 +1,8 @@ import React, { Component } from 'react'; import { render } from 'react-dom'; import AceEditor from '../src/ace.jsx'; -import brace from 'brace'; - - import 'brace/mode/jsx'; - const languages = [ 'javascript', 'java', @@ -41,6 +37,7 @@ const themes = [ languages.forEach((lang) => { require(`brace/mode/${lang}`) + require(`brace/snippets/${lang}`) }) themes.forEach((theme) => { @@ -48,6 +45,7 @@ themes.forEach((theme) => { }) /*eslint-disable no-alert, no-console */ import 'brace/ext/language_tools'; +import 'brace/ext/searchbox'; const defaultValue = diff --git a/example/split.html b/example/split.html new file mode 100644 index 00000000..3bfdef2c --- /dev/null +++ b/example/split.html @@ -0,0 +1,17 @@ + + + + + Split Editor + + + +
+
+

React-Ace: Split Editor Example

+
+
+
+ + + diff --git a/example/split.js b/example/split.js new file mode 100644 index 00000000..6465da39 --- /dev/null +++ b/example/split.js @@ -0,0 +1,287 @@ +import React, { Component } from 'react'; +import { render } from 'react-dom'; +import SplitAceEditor from '../src/split.jsx'; + +import 'brace/mode/jsx'; +import 'brace/ext/searchbox'; + +const languages = [ + 'javascript', + 'java', + 'python', + 'xml', + 'ruby', + 'sass', + 'markdown', + 'mysql', + 'json', + 'html', + 'handlebars', + 'golang', + 'csharp', + 'elixir', + 'typescript', + 'css' +] + +const themes = [ + 'monokai', + 'github', + 'tomorrow', + 'kuroir', + 'twilight', + 'xcode', + 'textmate', + 'solarized_dark', + 'solarized_light', + 'terminal', +] + +languages.forEach((lang) => { + require(`brace/mode/${lang}`) + require(`brace/snippets/${lang}`) +}) + +themes.forEach((theme) => { + require(`brace/theme/${theme}`) +}) +/*eslint-disable no-alert, no-console */ +import 'brace/ext/language_tools'; + + +const defaultValue = [ + `function onLoad(editor) { + console.log(\"i\'ve loaded\"); + }`, + 'const secondInput = "me i am the second input";' +]; +class App extends Component { + onLoad() { + console.log('i\'ve loaded'); + } + onChange(newValue) { + console.log('change', newValue); + this.setState({ + value: newValue + }) + } + + onSelectionChange(newValue, event) { + console.log('select-change', newValue); + console.log('select-change-event', event); + } + setTheme(e) { + this.setState({ + theme: e.target.value + }) + } + setMode(e) { + this.setState({ + mode: e.target.value + }) + } + setBoolean(name, value) { + this.setState({ + [name]: value + }) + } + setFontSize(e) { + this.setState({ + fontSize: parseInt(e.target.value,10) + }) + } + setSplits(e) { + this.setState({ + splits: parseInt(e.target.value,10) + }) + } + setOrientation(e) { + this.setState({ + orientation: e.target.value + }) + } + constructor(props) { + super(props); + this.state = { + splits: 2, + orientation: 'beside', + value: defaultValue, + theme: 'github', + mode: 'javascript', + enableBasicAutocompletion: false, + enableLiveAutocompletion: false, + fontSize: 14, + showGutter: true, + showPrintMargin: true, + highlightActiveLine: true, + enableSnippets: false, + showLineNumbers: true, + }; + this.setTheme = this.setTheme.bind(this); + this.setMode = this.setMode.bind(this); + this.onChange = this.onChange.bind(this); + this.setFontSize = this.setFontSize.bind(this); + this.setBoolean = this.setBoolean.bind(this); + this.setSplits = this.setSplits.bind(this); + this.setOrientation = this.setOrientation.bind(this); + } + render() { + return ( +
+
+
+ +

+ + + +

+
+ +
+ +

+ + +

+
+ +
+ +

+ + +

+
+ +
+ +

+ + +

+
+ +
+ +

+ + +

+
+
+

+ +

+
+
+

+ +

+
+
+

+ +

+
+
+

+ +

+
+
+

+ +

+
+
+

+ +

+
+
+

+ +

+
+ + +
+
+

Editor

+ +
+
+ ); + } +} + + +render( + , + document.getElementById('example') +); \ No newline at end of file diff --git a/package.json b/package.json index 86b06524..a33944ef 100644 --- a/package.json +++ b/package.json @@ -1,15 +1,15 @@ { "name": "react-ace", - "version": "4.4.0", + "version": "5.0.0", "description": "A react component for Ace Editor", - "main": "lib/ace.js", + "main": "lib/index.js", "types": "types.d.ts", "scripts": { "clean": "rimraf lib dist", - "lint": "node_modules/.bin/eslint src/ace.jsx", + "lint": "node_modules/.bin/eslint src/*", "build:lib": "babel src --out-dir lib", - "build:umd": "webpack src/ace.jsx dist/react-ace.js --config webpack.config.development.js", - "build:umd:min": "webpack src/ace.jsx dist/react-ace.min.js --config webpack.config.production.js", + "build:umd": "webpack src/index.js dist/react-ace.js --config webpack.config.development.js", + "build:umd:min": "webpack src/index.js dist/react-ace.min.js --config webpack.config.production.js", "example": "webpack-dev-server --config webpack.config.example.js", "build:example": "webpack --config webpack.config.example.js", "build": "npm run build:lib && npm run build:umd && npm run build:umd:min", @@ -25,7 +25,8 @@ "babel": { "presets": [ "es2015", - "react" + "react", + "stage-2" ], "plugins": [ "transform-object-rest-spread" @@ -41,6 +42,7 @@ "babel-plugin-transform-object-rest-spread": "^6.23.0", "babel-preset-es2015": "^6.24.1", "babel-preset-react": "^6.24.1", + "babel-preset-stage-2": "^6.24.1", "chai": "^3.5.0", "coveralls": "^2.13.1", "enzyme": "^2.4.1", @@ -53,7 +55,7 @@ "nyc": "^10.3.2", "react-addons-test-utils": "^15.5.1", "rimraf": "^2.5.2", - "sinon": "^2.2.0", + "sinon": "^2.3.2", "webpack": "^2.5.1", "webpack-dev-server": "^2.4.5" }, @@ -66,6 +68,7 @@ ], "dependencies": { "brace": "^0.10.0", + "lodash.get": "^4.4.2", "lodash.isequal": "^4.1.1", "opencollective": "^1.0.3", "prop-types": "^15.5.8" @@ -100,4 +103,4 @@ "url": "https://opencollective.com/react-ace", "logo": "https://opencollective.com/opencollective/logo.txt" } -} \ No newline at end of file +} diff --git a/src/ace.jsx b/src/ace.jsx index 7b4dc486..a1789159 100644 --- a/src/ace.jsx +++ b/src/ace.jsx @@ -4,33 +4,12 @@ import PropTypes from 'prop-types' import isEqual from 'lodash.isequal' const { Range } = ace.acequire('ace/range'); - -const editorOptions = [ - 'minLines', - 'maxLines', - 'readOnly', - 'highlightActiveLine', - 'tabSize', - 'enableBasicAutocompletion', - 'enableLiveAutocompletion', - 'enableSnippets', -]; +import { editorOptions, editorEvents } from './editorOptions.js' export default class ReactAce extends Component { constructor(props) { super(props); - [ - 'onChange', - 'onFocus', - 'onBlur', - 'onCopy', - 'onPaste', - 'onSelectionChange', - 'onScroll', - 'handleOptions', - 'updateRef', - ] - .forEach(method => { + editorEvents.forEach(method => { this[method] = this[method].bind(this); }); } @@ -95,6 +74,8 @@ export default class ReactAce extends Component { const option = editorOptions[i]; if (availableOptions.hasOwnProperty(option)) { this.editor.setOption(option, this.props[option]); + } else if (this.props[option]) { + console.warn(`ReaceAce: editor option ${option} was activated but not found. Did you need to import a related tool or did you possibly mispell the option?`) } } diff --git a/src/editorOptions.js b/src/editorOptions.js new file mode 100644 index 00000000..6a7ddbdb --- /dev/null +++ b/src/editorOptions.js @@ -0,0 +1,27 @@ +const editorOptions = [ + 'minLines', + 'maxLines', + 'readOnly', + 'highlightActiveLine', + 'tabSize', + 'enableBasicAutocompletion', + 'enableLiveAutocompletion', + 'enableSnippets', +] + +const editorEvents = [ + 'onChange', + 'onFocus', + 'onBlur', + 'onCopy', + 'onPaste', + 'onSelectionChange', + 'onScroll', + 'handleOptions', + 'updateRef', +] + +export { + editorOptions, + editorEvents +} \ No newline at end of file diff --git a/src/index.js b/src/index.js new file mode 100644 index 00000000..bc5b1647 --- /dev/null +++ b/src/index.js @@ -0,0 +1,6 @@ +import ace from './ace.jsx' +import split from './split.jsx'; +export { + split +} +export default ace \ No newline at end of file diff --git a/src/split.jsx b/src/split.jsx new file mode 100644 index 00000000..48690fca --- /dev/null +++ b/src/split.jsx @@ -0,0 +1,402 @@ +import ace from 'brace' +import React, { Component } from 'react' +import PropTypes from 'prop-types' +import isEqual from 'lodash.isequal' +import get from 'lodash.get' + +import { editorOptions, editorEvents } from './editorOptions.js' +const { Range } = ace.acequire('ace/range'); + +import 'brace/ext/split' +const { Split } = ace.acequire('ace/split'); + +export default class SplitComponent extends Component { + constructor(props) { + super(props); + editorEvents.forEach(method => { + this[method] = this[method].bind(this); + }); + } + + componentDidMount() { + const { + className, + onBeforeLoad, + mode, + focus, + theme, + fontSize, + value, + defaultValue, + cursorStart, + showGutter, + wrapEnabled, + showPrintMargin, + scrollMargin = [ 0, 0, 0, 0], + keyboardHandler, + onLoad, + commands, + annotations, + markers, + splits, + } = this.props; + + this.editor = ace.edit(this.refEditor); + + if (onBeforeLoad) { + onBeforeLoad(ace); + } + + const editorProps = Object.keys(this.props.editorProps); + + var split = new Split(this.editor.container,`ace/theme/${theme}`,splits) + this.editor.env.split = split; + + this.splitEditor = split.getEditor(0); + this.split = split + // in a split scenario we don't want a print margin for the entire application + this.editor.setShowPrintMargin(false); + this.editor.renderer.setShowGutter(false); + // get a list of possible options to avoid 'misspelled option errors' + const availableOptions = this.splitEditor.$options; + split.forEach((editor, index) => { + for (let i = 0; i < editorProps.length; i++) { + editor[editorProps[i]] = this.props.editorProps[editorProps[i]]; + } + const defaultValueForEditor = get(defaultValue, index) + const valueForEditor = get(value, index, '') + editor.setTheme(`ace/theme/${theme}`); + editor.renderer.setScrollMargin(scrollMargin[0], scrollMargin[1], scrollMargin[2], scrollMargin[3]) + editor.getSession().setMode(`ace/mode/${mode}`); + editor.setFontSize(fontSize); + editor.renderer.setShowGutter(showGutter); + editor.getSession().setUseWrapMode(wrapEnabled); + editor.setShowPrintMargin(showPrintMargin); + editor.on('focus', this.onFocus); + editor.on('blur', this.onBlur); + editor.on('copy', this.onCopy); + editor.on('paste', this.onPaste); + editor.on('change', this.onChange); + editor.getSession().selection.on('changeSelection', this.onSelectionChange); + editor.session.on('changeScrollTop', this.onScroll); + editor.setValue(defaultValueForEditor === undefined ? valueForEditor : defaultValueForEditor, cursorStart); + const newAnnotations = get(annotations, index, []) + const newMarkers = get(markers, index, []) + editor.getSession().setAnnotations(newAnnotations); + if(newMarkers && newMarkers.length > 0){ + this.handleMarkers(newMarkers,editor); + } + + for (let i = 0; i < editorOptions.length; i++) { + const option = editorOptions[i]; + if (availableOptions.hasOwnProperty(option)) { + editor.setOption(option, this.props[option]); + } else if (this.props[option]) { + console.warn(`ReaceAce: editor option ${option} was activated but not found. Did you need to import a related tool or did you possibly mispell the option?`) + } + } + this.handleOptions(this.props, editor); + if (Array.isArray(commands)) { + commands.forEach((command) => { + editor.commands.addCommand(command); + }); + } + + if (keyboardHandler) { + editor.setKeyboardHandler('ace/keyboard/' + keyboardHandler); + } + }) + + if (className) { + this.refEditor.className += ' ' + className; + } + + if (focus) { + this.splitEditor.focus(); + } + + const sp = this.editor.env.split; + sp.setOrientation( this.props.orientation === 'below' ? sp.BELOW : sp.BESIDE); + sp.resize(true) + if (onLoad) { + onLoad(sp); + } + } + + componentWillReceiveProps(nextProps) { + const oldProps = this.props; + + const split = this.editor.env.split + + if (nextProps.splits !== oldProps.splits) { + split.setSplits(nextProps.splits) + } + + if (nextProps.orientation !== oldProps.orientation) { + split.setOrientation( nextProps.orientation === 'below' ? split.BELOW : split.BESIDE); + } + + split.forEach((editor, index) => { + + if (nextProps.mode !== oldProps.mode) { + editor.getSession().setMode('ace/mode/' + nextProps.mode); + } + if (nextProps.keyboardHandler !== oldProps.keyboardHandler) { + if (nextProps.keyboardHandler) { + editor.setKeyboardHandler('ace/keyboard/' + nextProps.keyboardHandler); + } else { + editor.setKeyboardHandler(null); + } + } + if (nextProps.fontSize !== oldProps.fontSize) { + editor.setFontSize(nextProps.fontSize); + } + if (nextProps.wrapEnabled !== oldProps.wrapEnabled) { + editor.getSession().setUseWrapMode(nextProps.wrapEnabled); + } + if (nextProps.showPrintMargin !== oldProps.showPrintMargin) { + editor.setShowPrintMargin(nextProps.showPrintMargin); + } + if (nextProps.showGutter !== oldProps.showGutter) { + editor.renderer.setShowGutter(nextProps.showGutter); + } + + for (let i = 0; i < editorOptions.length; i++) { + const option = editorOptions[i]; + if (nextProps[option] !== oldProps[option]) { + editor.setOption(option, nextProps[option]); + } + } + if (!isEqual(nextProps.setOptions, oldProps.setOptions)) { + this.handleOptions(nextProps, editor); + } + const nextValue = get(nextProps.value, index, '') + if (editor.getValue() !== nextValue) { + // editor.setValue is a synchronous function call, change event is emitted before setValue return. + this.silent = true; + const pos = editor.session.selection.toJSON(); + editor.setValue(nextValue, nextProps.cursorStart); + editor.session.selection.fromJSON(pos); + this.silent = false; + } + const newAnnotations = get(nextProps.annotations, index, []) + const oldAnnotations = get(oldProps.annotations, index, []) + if (!isEqual(newAnnotations, oldAnnotations)) { + editor.getSession().setAnnotations(newAnnotations); + } + + const newMarkers = get(nextProps.markers, index, []) + const oldMarkers = get(oldProps.markers, index, []) + if (!isEqual(newMarkers, oldMarkers) && (newMarkers && newMarkers.length > 0)) { + this.handleMarkers(newMarkers, editor); + } + + }) + + if (nextProps.className !== oldProps.className) { + let appliedClasses = this.refEditor.className; + let appliedClassesArray = appliedClasses.trim().split(' '); + let oldClassesArray = oldProps.className.trim().split(' '); + oldClassesArray.forEach((oldClass) => { + let index = appliedClassesArray.indexOf(oldClass); + appliedClassesArray.splice(index, 1); + }); + this.refEditor.className = ' ' + nextProps.className + ' ' + appliedClassesArray.join(' '); + } + + if (nextProps.theme !== oldProps.theme) { + split.setTheme('ace/theme/' + nextProps.theme); + } + + if (nextProps.focus && !oldProps.focus) { + this.splitEditor.focus(); + } + if(nextProps.height !== this.props.height || nextProps.width !== this.props.width){ + this.editor.resize(); + } + } + + componentWillUnmount() { + this.editor.destroy(); + this.editor = null; + } + + onChange(event) { + if (this.props.onChange && !this.silent) { + let value = [] + this.editor.env.split.forEach((editor) => { + value.push(editor.getValue()) + }) + this.props.onChange(value, event); + } + } + + onSelectionChange(event) { + if (this.props.onSelectionChange) { + let value = [] + this.editor.env.split.forEach((editor) => { + value.push(editor.getSelection()) + }) + this.props.onSelectionChange(value, event); + } + } + + onFocus() { + if (this.props.onFocus) { + this.props.onFocus(); + } + } + + onBlur() { + if (this.props.onBlur) { + this.props.onBlur(); + } + } + + onCopy(text) { + if (this.props.onCopy) { + this.props.onCopy(text); + } + } + + onPaste(text) { + if (this.props.onPaste) { + this.props.onPaste(text); + } + } + + onScroll() { + if (this.props.onScroll) { + this.props.onScroll(this.editor); + } + } + + handleOptions(props, editor) { + const setOptions = Object.keys(props.setOptions); + for (let y = 0; y < setOptions.length; y++) { + editor.setOption(setOptions[y], props.setOptions[setOptions[y]]); + } + } + + handleMarkers(markers, editor) { + // remove foreground markers + let currentMarkers = editor.getSession().getMarkers(true); + for (const i in currentMarkers) { + if (currentMarkers.hasOwnProperty(i)) { + editor.getSession().removeMarker(currentMarkers[i].id); + } + } + // remove background markers + currentMarkers = editor.getSession().getMarkers(false); + for (const i in currentMarkers) { + if (currentMarkers.hasOwnProperty(i)) { + editor.getSession().removeMarker(currentMarkers[i].id); + } + } + // add new markers + markers.forEach(({ startRow, startCol, endRow, endCol, className, type, inFront = false }) => { + const range = new Range(startRow, startCol, endRow, endCol); + editor.getSession().addMarker(range, className, type, inFront); + }); + } + + updateRef(item) { + this.refEditor = item; + } + + render() { + const { name, width, height, style } = this.props; + const divStyle = { width, height, ...style }; + return ( +
+
+ ); + } +} + +SplitComponent.propTypes = { + mode: PropTypes.string, + splits: PropTypes.number, + orientation: PropTypes.string, + focus: PropTypes.bool, + theme: PropTypes.string, + name: PropTypes.string, + className: PropTypes.string, + height: PropTypes.string, + width: PropTypes.string, + fontSize: PropTypes.oneOfType([ + PropTypes.number, + PropTypes.string, + ]), + showGutter: PropTypes.bool, + onChange: PropTypes.func, + onCopy: PropTypes.func, + onPaste: PropTypes.func, + onFocus: PropTypes.func, + onBlur: PropTypes.func, + onScroll: PropTypes.func, + value: PropTypes.arrayOf(PropTypes.string), + defaultValue: PropTypes.arrayOf(PropTypes.string), + onLoad: PropTypes.func, + onSelectionChange: PropTypes.func, + onBeforeLoad: PropTypes.func, + minLines: PropTypes.number, + maxLines: PropTypes.number, + readOnly: PropTypes.bool, + highlightActiveLine: PropTypes.bool, + tabSize: PropTypes.number, + showPrintMargin: PropTypes.bool, + cursorStart: PropTypes.number, + editorProps: PropTypes.object, + setOptions: PropTypes.object, + style: PropTypes.object, + scrollMargin: PropTypes.array, + annotations: PropTypes.array, + markers: PropTypes.array, + keyboardHandler: PropTypes.string, + wrapEnabled: PropTypes.bool, + enableBasicAutocompletion: PropTypes.oneOfType([ + PropTypes.bool, + PropTypes.array, + ]), + enableLiveAutocompletion: PropTypes.oneOfType([ + PropTypes.bool, + PropTypes.array, + ]), + commands: PropTypes.array, +}; + +SplitComponent.defaultProps = { + name: 'brace-editor', + focus: false, + orientation: 'beside', + splits: 2, + mode: '', + theme: '', + height: '500px', + width: '500px', + value: [], + fontSize: 12, + showGutter: true, + onChange: null, + onPaste: null, + onLoad: null, + onScroll: null, + minLines: null, + maxLines: null, + readOnly: false, + highlightActiveLine: true, + showPrintMargin: true, + tabSize: 4, + cursorStart: 1, + editorProps: {}, + style: {}, + scrollMargin: [ 0, 0, 0, 0], + setOptions: {}, + wrapEnabled: false, + enableBasicAutocompletion: false, + enableLiveAutocompletion: false, +}; \ No newline at end of file diff --git a/tests/src/ace.spec.js b/tests/src/ace.spec.js index e6f60daa..81a55aca 100644 --- a/tests/src/ace.spec.js +++ b/tests/src/ace.spec.js @@ -5,7 +5,6 @@ import ace from 'brace'; import { mount } from 'enzyme'; import AceEditor from '../../src/ace.jsx'; import brace from 'brace'; // eslint-disable-line no-unused-vars - describe('Ace Component', () => { // Required for the document.getElementById used by Ace can work in the test environment @@ -21,6 +20,14 @@ describe('Ace Component', () => { expect(wrapper).to.exist; }); + it('should trigger console warn if editorOption is called', () => { + const stub = sinon.stub(console, 'warn'); + const wrapper = mount(, mountOptions); + expect(wrapper).to.exist; + expect(console.warn.calledWith('ReaceAce: editor option enableBasicAutocompletion was activated but not found. Did you need to import a related tool or did you possibly mispell the option?') ).to.be.true; + stub.restore(); + }); + it('should render without problems with defaults properties, defaultValue and keyboardHandler', () => { const wrapper = mount( { expect(onFocusCallback.callCount).to.equal(1); }); - it('should call the onSelectionChange method callback', () => { - const onSelectionChangeCallback = sinon.spy(); - const wrapper = mount(, mountOptions); - - // Check is not previously called - expect(onSelectionChangeCallback.callCount).to.equal(0); - - // Trigger the focus event + it('should call the onSelectionChange method callback', (done) => { + let onSelectionChange = function(){} + const value = ` + function main(value) { + console.log('hi james') + return value; + } + `; + const wrapper = mount(, mountOptions); + + onSelectionChange = function(selection) { + const content = wrapper.instance().editor.session.getTextRange(selection.getRange()); + expect(content).to.equal(value) + done() + } + wrapper.setProps({onSelectionChange}) wrapper.instance().editor.getSession().selection.selectAll() - - expect(onSelectionChangeCallback.callCount).to.equal(1); }); it('should call the onBlur method callback', () => { @@ -399,6 +412,7 @@ describe('Ace Component', () => { expect(onFocusCallback.callCount).to.equal(1); }); + }); }); diff --git a/tests/src/split.spec.js b/tests/src/split.spec.js new file mode 100644 index 00000000..44b6b053 --- /dev/null +++ b/tests/src/split.spec.js @@ -0,0 +1,433 @@ +import { expect } from 'chai'; +import React from 'react'; +import sinon from 'sinon'; +import ace from 'brace'; +import { mount } from 'enzyme'; +import SplitEditor from '../../src/split.jsx'; +import brace from 'brace'; // eslint-disable-line no-unused-vars + +describe('Split Component', () => { + + // Required for the document.getElementById used by Ace can work in the test environment + const domElement = document.getElementById('app'); + const mountOptions = { + attachTo: domElement, + }; + + describe('General', () => { + sinon.restore() + it('should render without problems with defaults properties', () => { + const wrapper = mount(, mountOptions); + expect(wrapper).to.exist; + }); + it('should get the ace library from the onBeforeLoad callback', () => { + const beforeLoadCallback = sinon.spy(); + mount(, mountOptions); + + expect(beforeLoadCallback.callCount).to.equal(1); + expect(beforeLoadCallback.getCall(0).args[0]).to.deep.equal(ace); + }); + + it('should trigger console warn if editorOption is called', () => { + const stub = sinon.stub(console, 'warn'); + const wrapper = mount(, mountOptions); + expect(wrapper).to.exist; + expect(console.warn.calledWith('ReaceAce: editor option enableBasicAutocompletion was activated but not found. Did you need to import a related tool or did you possibly mispell the option?') ).to.be.true; + stub.restore(); + }); + + it('should set the editor props to the Ace element', () => { + const editorProperties = { + react: 'setFromReact', + test: 'setFromTest', + }; + const wrapper = mount(, mountOptions); + + const editor = wrapper.instance().splitEditor; + + expect(editor.react).to.equal(editorProperties.react); + expect(editor.test).to.equal(editorProperties.test); + }); + + it('should update the orientation on componentWillReceiveProps', () => { + let orientation = 'below'; + const wrapper = mount(, mountOptions); + + // Read set value + let editor = wrapper.instance().split; + expect(editor.getOrientation()).to.equal(editor.BELOW); + + // Now trigger the componentWillReceiveProps + orientation = 'beside'; + wrapper.setProps({orientation}); + editor = wrapper.instance().split; + expect(editor.getOrientation()).to.equal(editor.BESIDE); + }); + + it('should update the orientation on componentWillReceiveProps', () => { + const wrapper = mount(, mountOptions); + + // Read set value + let editor = wrapper.instance().split; + expect(editor.getSplits()).to.equal(2); + + // Now trigger the componentWillReceiveProps + wrapper.setProps({splits: 4}); + editor = wrapper.instance().split; + expect(editor.getSplits()).to.equal(4); + }); + + it('should set the command for the Ace element', () => { + const commandsMock = [ + { + name: 'myReactAceTest', + bindKey: {win: 'Ctrl-M', mac: 'Command-M'}, + exec: () => { + }, + readOnly: true + }, + { + name: 'myTestCommand', + bindKey: {win: 'Ctrl-W', mac: 'Command-W'}, + exec: () => { + }, + readOnly: true + } + ]; + const wrapper = mount(, mountOptions); + + const editor = wrapper.instance().splitEditor; + expect(editor.commands.commands.myReactAceTest).to.deep.equal(commandsMock[0]); + expect(editor.commands.commands.myTestCommand).to.deep.equal(commandsMock[1]); + }); + + it('should get the editor from the onLoad callback', () => { + const loadCallback = sinon.spy(); + const wrapper = mount(, mountOptions); + + // Get the editor + const editor = wrapper.instance().split; + + expect(loadCallback.callCount).to.equal(1); + expect(loadCallback.getCall(0).args[0]).to.deep.equal(editor); + }); + + it('should trigger the focus on mount', () => { + const onFocusCallback = sinon.spy(); + mount(, mountOptions); + + // Read the focus + expect(onFocusCallback.callCount).to.equal(1); + }); + + + it('should set editor to null on componentWillUnmount', () => { + const wrapper = mount(, mountOptions); + expect(wrapper.node.editor).to.not.equal(null); + + // Check the editor is null after the Unmount + wrapper.unmount(); + expect(wrapper.node.editor).to.equal(null); + }); + + + + }); + + describe('Events', () => { + + it('should call the onChange method callback', () => { + const onChangeCallback = sinon.spy(); + const wrapper = mount(, mountOptions); + + // Check is not previously called + expect(onChangeCallback.callCount).to.equal(0); + + // Trigger the change event + const expectText = 'React Ace Test'; + wrapper.instance().splitEditor.setValue(expectText, 1); + + expect(onChangeCallback.callCount).to.equal(1); + expect(onChangeCallback.getCall(0).args[0]).to.deep.equal([expectText, '']); + expect(onChangeCallback.getCall(0).args[1].action).to.eq('insert') + }); + + it('should call the onCopy method', () => { + const onCopyCallback = sinon.spy(); + const wrapper = mount(, mountOptions); + + // Check is not previously called + expect(onCopyCallback.callCount).to.equal(0); + + // Trigger the copy event + const expectText = 'React Ace Test'; + wrapper.instance().onCopy(expectText); + + expect(onCopyCallback.callCount).to.equal(1); + expect(onCopyCallback.getCall(0).args[0]).to.equal(expectText); + }); + + it('should call the onPaste method', () => { + const onPasteCallback = sinon.spy(); + const wrapper = mount(, mountOptions); + + // Check is not previously called + expect(onPasteCallback.callCount).to.equal(0); + + // Trigger the Paste event + const expectText = 'React Ace Test'; + wrapper.instance().onPaste(expectText); + + expect(onPasteCallback.callCount).to.equal(1); + expect(onPasteCallback.getCall(0).args[0]).to.equal(expectText); + }); + + it('should call the onFocus method callback', () => { + const onFocusCallback = sinon.spy(); + const wrapper = mount(, mountOptions); + + // Check is not previously called + expect(onFocusCallback.callCount).to.equal(0); + + // Trigger the focus event + wrapper.instance().split.focus(); + + expect(onFocusCallback.callCount).to.equal(1); + }); + + it('should call the onSelectionChange method callback', () => { + const onSelectionChangeCallback = sinon.spy(); + const wrapper = mount(, mountOptions); + + // Check is not previously called + expect(onSelectionChangeCallback.callCount).to.equal(0); + + // Trigger the focus event + wrapper.instance().splitEditor.getSession().selection.selectAll() + + expect(onSelectionChangeCallback.callCount).to.equal(1); + }); + + it('should call the onBlur method callback', () => { + const onBlurCallback = sinon.spy(); + const wrapper = mount(, mountOptions); + + // Check is not previously called + expect(onBlurCallback.callCount).to.equal(0); + + // Trigger the blur event + wrapper.instance().onBlur(); + + expect(onBlurCallback.callCount).to.equal(1); + }); + + it('should not trigger a component error to call the events without setting the props', () => { + const wrapper = mount(, mountOptions); + + // Check the if statement is checking if the property is set. + wrapper.instance().onChange(); + wrapper.instance().onCopy('copy'); + wrapper.instance().onPaste('paste'); + wrapper.instance().onFocus(); + wrapper.instance().onBlur(); + }); + + }); + describe('ComponentWillReceiveProps', () => { + + it('should update the editorOptions on componentWillReceiveProps', () => { + const options = { + printMargin: 80 + }; + const wrapper = mount(, mountOptions); + + // Read set value + const editor = wrapper.instance().splitEditor; + expect(editor.getOption('printMargin')).to.equal(options.printMargin); + + // Now trigger the componentWillReceiveProps + const newOptions = { + printMargin: 200, + animatedScroll: true, + }; + wrapper.setProps({setOptions: newOptions}); + expect(editor.getOption('printMargin')).to.equal(newOptions.printMargin); + expect(editor.getOption('animatedScroll')).to.equal(newOptions.animatedScroll); + }); + it('should update the editorOptions on componentWillReceiveProps', () => { + + const wrapper = mount(, mountOptions); + + // Read set value + const editor = wrapper.instance().splitEditor; + expect(editor.getOption('minLines')).to.equal(1); + + + wrapper.setProps({minLines: 2}); + expect(editor.getOption('minLines')).to.equal(2); + }); + + + it('should update the mode on componentWillReceiveProps', () => { + + const wrapper = mount(, mountOptions); + + // Read set value + const oldMode = wrapper.first('SplitEditor').props() + + wrapper.setProps({mode: 'elixir'}); + const newMode = wrapper.first('SplitEditor').props() + expect(oldMode).to.not.deep.equal(newMode); + }); + + + + + it('should update many props on componentWillReceiveProps', () => { + + const wrapper = mount(( + ), mountOptions); + + // Read set value + const oldMode = wrapper.first('SplitEditor').props() + + wrapper.setProps({ + theme: 'solarized', + keyboardHandler: 'emacs', + fontSize: 18, + wrapEnabled: false, + showPrintMargin: false, + showGutter: true, + height: '120px', + width: '220px', + }); + const newMode = wrapper.first('SplitEditor').props() + expect(oldMode).to.not.deep.equal(newMode); + }); + + + + it('should update the className on componentWillReceiveProps', () => { + const className = 'old-class'; + const wrapper = mount(, mountOptions); + + // Read set value + let editor = wrapper.node.refEditor; + expect(editor.className).to.equal(' ace_editor ace-tm old-class'); + + // Now trigger the componentWillReceiveProps + const newClassName = 'new-class'; + wrapper.setProps({className: newClassName}); + editor = wrapper.node.refEditor; + expect(editor.className).to.equal(' new-class ace_editor ace-tm'); + }); + + + it('should update the value on componentWillReceiveProps', () => { + const startValue = 'start value'; + const anotherStartValue = 'another start value'; + const wrapper = mount(, mountOptions); + + // Read set value + let editor = wrapper.instance().split.getEditor(0); + let editor2 = wrapper.instance().split.getEditor(1); + expect(editor.getValue()).to.equal(startValue); + expect(editor2.getValue()).to.equal(anotherStartValue); + + // Now trigger the componentWillReceiveProps + const newValue = 'updated value'; + const anotherNewValue = 'another updated value'; + wrapper.setProps({value: [newValue, anotherNewValue]}); + editor = wrapper.instance().splitEditor; + editor2 = wrapper.instance().split.getEditor(1); + expect(editor.getValue()).to.equal(newValue); + expect(editor2.getValue()).to.equal(anotherNewValue); + }); + it('should set up the markers', () => { + const markers = [[{ + startRow: 3, + type: 'text', + className: 'test-marker' + }]]; + const wrapper = mount(, mountOptions); + + // Read the markers + const editor = wrapper.instance().splitEditor; + expect(editor.getSession().getMarkers()['3'].clazz).to.equal('test-marker'); + expect(editor.getSession().getMarkers()['3'].type).to.equal('text'); + }); + + it('should update the markers', () => { + const oldMarkers = [[ + { + startRow: 4, + type: 'text', + className: 'test-marker-old' + }, + { + startRow: 7, + type: 'foo', + className: 'test-marker-old', + inFront: true + } + ]]; + const markers = [[{ + startRow: 3, + type: 'text', + className: 'test-marker-new', + inFront: true, + },{ + startRow: 5, + type: 'text', + className: 'test-marker-new' + }]]; + const wrapper = mount(, mountOptions); + + // Read the markers + const editor = wrapper.instance().splitEditor; + expect(editor.getSession().getMarkers()['3'].clazz).to.equal('test-marker-old'); + expect(editor.getSession().getMarkers()['3'].type).to.equal('text'); + wrapper.setProps({markers: markers}); + const editorB = wrapper.instance().splitEditor; + expect(editorB.getSession().getMarkers()['6'].clazz).to.equal('test-marker-new'); + expect(editorB.getSession().getMarkers()['6'].type).to.equal('text'); + }); + + it('should add annotations', () => { + const annotations = [{ + row: 3, // must be 0 based + column: 4, // must be 0 based + text: 'error.message', // text to show in tooltip + type: 'error' + }] + const wrapper = mount(, mountOptions); + const editor = wrapper.instance().splitEditor; + wrapper.setProps({annotations: [annotations]}); + expect(editor.getSession().getAnnotations()).to.deep.equal(annotations); + wrapper.setProps({annotations: null}); + expect(editor.getSession().getAnnotations()).to.deep.equal([]); + }) + + it('should trigger the focus on componentWillReceiveProps', () => { + const onFocusCallback = sinon.spy(); + const wrapper = mount(, mountOptions); + + // Read the focus + expect(onFocusCallback.callCount).to.equal(0); + + // Now trigger the componentWillReceiveProps + wrapper.setProps({focus: true}); + expect(onFocusCallback.callCount).to.equal(1); + }); + + }); +}); \ No newline at end of file diff --git a/webpack.config.example.js b/webpack.config.example.js index ae69b22c..a2eb929d 100644 --- a/webpack.config.example.js +++ b/webpack.config.example.js @@ -4,12 +4,13 @@ const path = require('path'); module.exports = { devtool: 'source-map', - entry: [ - './example/index', - ], + entry: { + 'index': './example/index', + 'split': './example/split', + }, output: { path: path.join(__dirname, 'example/static'), - filename: 'bundle.js', + filename: '[name].js', publicPath: '/static/', }, plugins: [ @@ -25,7 +26,7 @@ module.exports = { }, devServer: { hot: true, - contentBase: path.join(__dirname, 'example'), + contentBase: [path.join(__dirname, 'example'), path.join(__dirname, 'dist')], compress: true, port: 9000, },