Repository: couchdb-fauxton Updated Branches: refs/heads/master 9d7cd4421 -> f5f375c59
Refactoring <CodeEditor /> This PR refactors the <CodeEditor /> component to make it more generic. Ace editor instances such as the mango, index editor, and Zen mode now use this via wrapper components (<CodeEditorPanel /> and <ZenModeOverlay />). The goal is to make this the one any only place where the ace editor is instantiated. It'll also be needed for the next step: refactoring the full page doc editor. Also: Removed extra code to clear notifications in <Editor />. This was redundant since the "there have been changed" popup doesn't (and shouldn't, I'd argue) appear for small editors. Project: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/repo Commit: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/commit/f5f375c5 Tree: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/tree/f5f375c5 Diff: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/diff/f5f375c5 Branch: refs/heads/master Commit: f5f375c59c2f43577a3457b2652bc0d066cc1466 Parents: 9d7cd44 Author: Ben Keen <[email protected]> Authored: Wed Jun 10 11:41:06 2015 -0700 Committer: Ben Keen <[email protected]> Committed: Tue Jun 16 12:36:36 2015 -0700 ---------------------------------------------------------------------- .../components/react-components.react.jsx | 446 +++++++++++-------- .../tests/codeEditorPanelSpec.react.jsx | 66 +++ app/addons/components/tests/codeEditorSpec.js | 134 ------ .../components/tests/codeEditorSpec.react.jsx | 103 +---- .../components/tests/zenModeSpec.react.jsx | 5 +- .../documents/index-editor/components.react.jsx | 31 +- .../tests/viewIndex.componentsSpec.react.jsx | 6 +- .../documents/mango/mango.components.react.jsx | 11 +- 8 files changed, 359 insertions(+), 443 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/f5f375c5/app/addons/components/react-components.react.jsx ---------------------------------------------------------------------- diff --git a/app/addons/components/react-components.react.jsx b/app/addons/components/react-components.react.jsx index d543a09..1186c83 100644 --- a/app/addons/components/react-components.react.jsx +++ b/app/addons/components/react-components.react.jsx @@ -64,6 +64,132 @@ function (app, FauxtonAPI, React, Components, ace, beautifyHelper) { } }); + + /** + * A pre-packaged JS editor panel for use on the Edit Index / Mango pages. Includes options for a title, zen mode + * icon and beautify button. + */ + var CodeEditorPanel = React.createClass({ + getDefaultProps: function () { + return { + id: 'code-editor', + defaultCode: '', + title: '', + docLink: '', + allowZenMode: true, + blur: function () {} + }; + }, + + getInitialState: function () { + return this.getStoreState(); + }, + + getStoreState: function () { + return { + zenModeEnabled: false, + code: this.props.defaultCode + }; + }, + + componentWillReceiveProps: function (nextProps) { + if (nextProps.defaultCode !== this.props.defaultCode) { + this.setState({ code: nextProps.defaultCode }); + } + }, + + // list of JSHINT errors to ignore: gets around problem of anonymous functions not being valid + ignorableErrors: [ + 'Missing name in function declaration.', + "['{a}'] is better written in dot notation." + ], + + getZenModeIcon: function () { + if (this.props.allowZenMode) { + return <span className="fonticon fonticon-resize-full zen-editor-icon" title="Enter Zen mode" onClick={this.enterZenMode}></span>; + } + }, + + getDocIcon: function () { + if (this.props.docLink) { + return ( + <a className="help-link" + data-bypass="true" + href={this.props.docLink} + target="_blank" + > + <i className="icon-question-sign"></i> + </a> + ); + } + }, + + getZenModeOverlay: function () { + if (this.state.zenModeEnabled) { + return ( + <ZenModeOverlay + defaultCode={this.state.code} + mode={this.props.mode} + ignorableErrors={this.ignorableErrors} + onExit={this.exitZenMode} + /> + ); + } + }, + + enterZenMode: function () { + this.setState({ + zenModeEnabled: true, + code: this.refs.codeEditor.getValue() + }); + }, + + exitZenMode: function (content) { + this.setState({ + zenModeEnabled: false, + code: content + }); + }, + + getEditor: function () { + return this.refs.codeEditor; + }, + + getValue: function () { + return this.getEditor().getValue(); + }, + + beautify: function (code) { + this.setState({ code: code }); + }, + + render: function () { + return ( + <div className="control-group"> + <label> + <strong>{this.props.title + ' '}</strong> + {this.getDocIcon()} + {this.getZenModeIcon()} + </label> + <CodeEditor + id={this.props.id} + ref="codeEditor" + mode="javascript" + defaultCode={this.state.code} + showGutter={true} + ignorableErrors={this.ignorableErrors} + setHeightToLineCount={true} + blur={this.props.blur} + /> + <Beautify code={this.state.code} beautifiedCode={this.beautify} /> + {this.getZenModeOverlay()} + </div> + ); + } + }); + + + // a generic Ace Editor component. This should be the only place in the app that instantiates an editor var CodeEditor = React.createClass({ getDefaultProps: function () { return { @@ -71,82 +197,106 @@ function (app, FauxtonAPI, React, Components, ace, beautifyHelper) { mode: 'javascript', theme: 'idle_fingers', fontSize: 13, - code: '', - showEditorOnly: false, + defaultCode: '', showGutter: true, highlightActiveLine: true, showPrintMargin: false, autoScrollEditorIntoView: true, - setHeightWithJS: true, - isFullPageEditor: false, - disableUnload: false, - allowZenMode: true, - allowAnonFunction: true, // allows JS fragments, like `function (doc) { emit(doc._id, 1); `}. Suppresses JS errors - change: function () {} + autoFocus: false, + + // these two options create auto-resizeable code editors, with a maximum number of lines + setHeightToLineCount: false, + maxLines: 10, + + // optional editor key commands (e.g. specific save action) + editorCommands: [], + + // notifies users that there is unsaved changes in the editor when navigating away from the page + notifyUnsavedChanges: false, + + // an optional array of ignorable Ace editors. Lets us filter out errors based on context + ignorableErrors: [], + + // un-Reacty, but the code editor is a self-contained component and it's helpful to be able to tie into + // editor specific events like content changes and leaving the editor + change: function () {}, + blur: function () {} }; }, + // used purely to keep track of content changes. This is only reset via an explicit clearChanges() call getInitialState: function () { - return this.getStoreState(); - }, - - getStoreState: function () { return { - zenModeEnabled: false + originalCode: this.props.defaultCode }; }, hasChanged: function () { - return !_.isEqual(this.props.code, this.getValue()); + return !_.isEqual(this.state.originalCode, this.getValue()); }, - setupAce: function (props, shouldUpdateCode) { - var el = this.refs.ace.getDOMNode(); + clearChanges: function () { + this.setState({ + originalCode: this.getValue() + }); + }, - //set the id so our nightwatch tests can find it - el.id = props.id; + setupAce: function (props, shouldUpdateCode) { + this.editor = ace.edit(this.refs.ace.getDOMNode()); - this.editor = ace.edit(el); - // Automatically scrolling cursor into view after selection - // change this will be disabled in the next version - // set editor.$blockScrolling = Infinity to disable this message + // suppresses an Ace editor error this.editor.$blockScrolling = Infinity; if (shouldUpdateCode) { - this.setEditorValue(props.code); + this.setEditorValue(props.defaultCode); } this.editor.setShowPrintMargin(props.showPrintMargin); this.editor.autoScrollEditorIntoView = props.autoScrollEditorIntoView; this.editor.setOption('highlightActiveLine', this.props.highlightActiveLine); - this.setHeightToLineCount(); - if (this.props.allowAnonFunction) { - this.removeIncorrectAnnotations(this.editor); + if (this.props.setHeightToLineCount) { + this.setHeightToLineCount(); + } + + if (this.props.ignorableErrors) { + this.removeIgnorableAnnotations(); } - this.editor.getSession().setMode("ace/mode/" + props.mode); - this.editor.setTheme("ace/theme/" + props.theme); + this.addCommands(); + this.editor.getSession().setMode('ace/mode/' + props.mode); + this.editor.setTheme('ace/theme/' + props.theme); this.editor.setFontSize(props.fontSize); this.editor.getSession().setUseSoftTabs(true); + + if (this.props.autoFocus) { + this.editor.focus(); + } }, - onChange: function () { - this.setState(this.getStoreState()); + addCommands: function () { + _.each(this.props.editorCommands, function (command) { + this.editor.commands.addCommand(command); + }, this); }, setupEvents: function () { - this.editor.on('blur', _.bind(this.saveCodeChange, this)); - - if (this.props.disableUnload) { - return; + this.editor.on('blur', _.bind(this.onBlur, this)); + this.editor.on('change', _.bind(this.onContentChange, this)); + if (this.props.notifyUnsavedChanges) { + $(window).on('beforeunload.editor_' + this.props.id, _.bind(this.quitWarningMsg)); + FauxtonAPI.beforeUnload('editor_' + this.props.id, _.bind(this.quitWarningMsg, this)); } + }, - $(window).on('beforeunload.editor_' + this.props.id, _.bind(this.quitWarningMsg)); - FauxtonAPI.beforeUnload('editor_' + this.props.id, _.bind(this.quitWarningMsg, this)); + onBlur: function () { + this.props.blur(this.getValue()); }, - saveCodeChange: function () { + onContentChange: function () { + if (this.props.setHeightToLineCount) { + this.setHeightToLineCount(); + } this.props.change(this.getValue()); }, @@ -157,55 +307,17 @@ function (app, FauxtonAPI, React, Components, ace, beautifyHelper) { }, removeEvents: function () { - if (this.props.disableUnload) { - return; + if (this.props.notifyUnsavedChanges) { + $(window).off('beforeunload.editor_' + this.props.id); + FauxtonAPI.removeBeforeUnload('editor_' + this.props.id); } - - $(window).off('beforeunload.editor_' + this.props.id); - FauxtonAPI.removeBeforeUnload('editor_' + this.props.id); }, setHeightToLineCount: function () { - if (!this.props.setHeightWithJS) { - return; - } - - var lines = this.editor.getSession().getDocument().getLength(); - - if (this.props.isFullPageEditor) { - var maxLines = this.getMaxAvailableLinesOnPage(); - lines = lines < maxLines ? lines : maxLines; - } + var numLines = this.editor.getSession().getDocument().getLength(); + var maxLines = (numLines > this.props.maxLines) ? this.props.maxLines : numLines; this.editor.setOptions({ - maxLines: lines - }); - }, - - // List of JSHINT errors to ignore - // Gets around problem of anonymous functions not being a valid statement - excludedViewErrors: [ - "Missing name in function declaration.", - "['{a}'] is better written in dot notation." - ], - - isIgnorableError: function (msg) { - return _.contains(this.excludedViewErrors, msg); - }, - - removeIncorrectAnnotations: function (editor) { - var isIgnorableError = this.isIgnorableError; - editor.getSession().on("changeAnnotation", function () { - var annotations = editor.getSession().getAnnotations(); - var newAnnotations = _.reduce(annotations, function (annotations, error) { - if (!isIgnorableError(error.raw)) { - annotations.push(error); - } - return annotations; - }, []); - - if (annotations.length !== newAnnotations.length) { - editor.getSession().setAnnotations(newAnnotations); - } + maxLines: maxLines }); }, @@ -220,56 +332,45 @@ function (app, FauxtonAPI, React, Components, ace, beautifyHelper) { }, componentWillReceiveProps: function (nextProps) { - var codeChanged = !_.isEqual(nextProps.code, this.getValue()); + var codeChanged = !_.isEqual(nextProps.defaultCode, this.getValue()); this.setupAce(nextProps, codeChanged); }, - editSaved: function () { - return this.hasChanged(); - }, - - zenModeIcon: function () { - if (this.props.allowZenMode) { - return <span className="fonticon fonticon-resize-full zen-editor-icon" title="Enter Zen mode" onClick={this.enterZenMode}></span>; - } + getAnnotations: function () { + return this.editor.getSession().getAnnotations(); }, - enterZenMode: function () { - this.setState({ zenModeEnabled: true }); + isIgnorableError: function (msg) { + return _.contains(this.props.ignorableErrors, msg); }, - getTitleFragment: function () { - if (!this.props.docs) { - return (<strong>{this.props.title}</strong>); - } - - return ( - <label> - <strong>{this.props.title + ' '}</strong> - <a - className="help-link" - data-bypass="true" - href={this.props.docs} - target="_blank" - > - <i className="icon-question-sign"></i> - </a> - {this.zenModeIcon()} - </label> - ); - }, + removeIgnorableAnnotations: function () { + var isIgnorableError = this.isIgnorableError; + this.editor.getSession().on('changeAnnotation', function () { + var annotations = this.editor.getSession().getAnnotations(); + var newAnnotations = _.reduce(annotations, function (annotations, error) { + if (!isIgnorableError(error.raw)) { + annotations.push(error); + } + return annotations; + }, []); - getAnnotations: function () { - return this.editor.getSession().getAnnotations(); + if (annotations.length !== newAnnotations.length) { + this.editor.getSession().setAnnotations(newAnnotations); + } + }.bind(this)); }, + // ------------------ + // TODO two things to do after full page doc editor refactor: + // 1. rename to hasErrors() hadValidCode: function () { var errors = this.getAnnotations(); - // By default CouchDB view functions don't pass lint return _.every(errors, function (error) { return this.isIgnorableError(error.raw); }, this); }, + // ------------------ setEditorValue: function (code, lineNumber) { lineNumber = lineNumber ? lineNumber : -1; @@ -280,41 +381,9 @@ function (app, FauxtonAPI, React, Components, ace, beautifyHelper) { return this.editor.getValue(); }, - getEditor: function () { - return this; - }, - - getZenModeOverlay: function () { - if (this.state.zenModeEnabled) { - return ( - <ZenModeOverlay - visible={this.state.zenModeEnabled} - defaultCode={this.getValue()} - mode={this.props.mode} - removeIncorrectAnnotations={this.removeIncorrectAnnotations} - onExit={this.onExitZenMode} - /> - ); - } - }, - - onExitZenMode: function (content) { - this.setEditorValue(content); - this.setState({ zenModeEnabled: false }); - }, - render: function () { - if (this.props.showEditorOnly) { - return (<div ref="ace" className="js-editor" id={this.props.id}></div>); - } - return ( - <div className="control-group"> - {this.getTitleFragment()} - <div ref="ace" className="js-editor" id={this.props.id}></div> - <Beautify code={this.props.code} beautifiedCode={this.setEditorValue} /> - {this.getZenModeOverlay()} - </div> + <div ref="ace" className="js-editor" id={this.props.id}></div> ); } }); @@ -330,12 +399,17 @@ function (app, FauxtonAPI, React, Components, ace, beautifyHelper) { return { mode: 'javascript', defaultCode: '', - removeIncorrectAnnotations: null, + ignorableErrors: [], onExit: null, highlightActiveLine: false }; }, + themes: { + dark: 'idle_fingers', + light: 'dawn' + }, + getInitialState: function () { return this.getStoreState(); }, @@ -357,64 +431,41 @@ function (app, FauxtonAPI, React, Components, ace, beautifyHelper) { }, componentDidMount: function () { - this.update(); - this.editor.focus(); $(this.refs.exit.getDOMNode()).tooltip({ placement: 'left' }); $(this.refs.theme.getDOMNode()).tooltip({ placement: 'left' }); }, - componentDidUpdate: function () { - this.update(); + exitZenMode: function () { + this.props.onExit(this.getValue()); }, - exitZenMode: function () { - this.props.onExit(this.editor.getValue()); + getValue: function () { + return this.refs.ace.getValue(); }, toggleTheme: function () { var newTheme = (this.state.theme === 'dark') ? 'light' : 'dark'; this.setState({ theme: newTheme, - code: this.editor.getValue() + code: this.getValue() }); app.utils.localStorageSet('zenTheme', newTheme); }, - update: function () { - var el = this.refs.ace.getDOMNode(); - this.editor = ace.edit(el); - this.editor.$blockScrolling = Infinity; - this.setEditorValue(this.state.code); - - var theme = this.state.theme === 'dark' ? 'idle_fingers' : 'dawn'; - this.editor.setTheme('ace/theme/' + theme); - this.editor.getSession().setMode('ace/mode/' + this.props.mode); - this.editor.getSession().setUseSoftTabs(true); - this.editor.setOption('highlightActiveLine', this.props.highlightActiveLine); - this.editor.setShowPrintMargin(false); - - // escape exits zen mode. Add the key binding - this.editor.commands.addCommand({ - name: "close", - bindKey: { win: "ESC", mac: "ESC" }, - exec: function () { - this.exitZenMode(); - }.bind(this) - }); - - // if an annotation removal method has been passed, ensure it's called so that error messages are cleaned - if (this.props.removeIncorrectAnnotations) { - this.props.removeIncorrectAnnotations(this.editor); - } - }, - setEditorValue: function (code, lineNumber) { lineNumber = lineNumber ? lineNumber : -1; this.editor.setValue(code, lineNumber); }, render: function () { - var classes = "full-page-editor-modal-wrapper zen-theme-" + this.state.theme; + var classes = 'full-page-editor-modal-wrapper zen-theme-' + this.state.theme; + + var editorCommands = [{ + name: 'close', + bindKey: { win: 'ESC', mac: 'ESC' }, + exec: this.exitZenMode + }]; + return ( <div className={classes}> <div className="zen-mode-controls"> @@ -438,7 +489,15 @@ function (app, FauxtonAPI, React, Components, ace, beautifyHelper) { </ul> <div className="tooltips"></div> </div> - <div ref="ace" className="js-editor"></div> + <CodeEditor + ref="ace" + autoFocus={true} + theme={this.themes[this.state.theme]} + defaultCode={this.props.defaultCode} + editorCommands={editorCommands} + ignorableErrors={this.props.ignorableErrors} + highlightActiveLine={this.props.highlightActiveLine} + /> </div> ); } @@ -503,7 +562,6 @@ function (app, FauxtonAPI, React, Components, ace, beautifyHelper) { }); var Document = React.createClass({ - propTypes: { docIdentifier: React.PropTypes.string.isRequired, docChecked: React.PropTypes.func.isRequired @@ -534,7 +592,6 @@ function (app, FauxtonAPI, React, Components, ace, beautifyHelper) { }, getCheckbox: function () { - if (!this.props.isDeletable) { return <div className="checkbox-dummy"></div>; } @@ -594,9 +651,7 @@ function (app, FauxtonAPI, React, Components, ace, beautifyHelper) { }); var LoadLines = React.createClass({ - render: function () { - return ( <div className="loading-lines"> <div id="line1"> </div> @@ -606,7 +661,6 @@ function (app, FauxtonAPI, React, Components, ace, beautifyHelper) { </div> ); } - }); var ConfirmButton = React.createClass({ @@ -660,8 +714,8 @@ function (app, FauxtonAPI, React, Components, ace, beautifyHelper) { <ul className="dropdown-menu arrow" key={key} role="menu" aria-labelledby="dLabel"> {this.createSectionTitle(linkSection.title)} {this.createSectionLinks(linkSection.links)} - </ul> - ); + </ul> + ); }.bind(this)); }, @@ -676,10 +730,12 @@ function (app, FauxtonAPI, React, Components, ace, beautifyHelper) { }); - var ReactComponents = { + + return { ConfirmButton: ConfirmButton, ToggleHeaderButton: ToggleHeaderButton, StyledSelect: StyledSelect, + CodeEditorPanel: CodeEditorPanel, CodeEditor: CodeEditor, ZenModeOverlay: ZenModeOverlay, Beautify: Beautify, @@ -689,6 +745,4 @@ function (app, FauxtonAPI, React, Components, ace, beautifyHelper) { MenuDropDown: MenuDropDown }; - return ReactComponents; - }); http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/f5f375c5/app/addons/components/tests/codeEditorPanelSpec.react.jsx ---------------------------------------------------------------------- diff --git a/app/addons/components/tests/codeEditorPanelSpec.react.jsx b/app/addons/components/tests/codeEditorPanelSpec.react.jsx new file mode 100644 index 0000000..b0bbcce --- /dev/null +++ b/app/addons/components/tests/codeEditorPanelSpec.react.jsx @@ -0,0 +1,66 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. +define([ + 'api', + 'addons/components/react-components.react', + 'testUtils', + 'react' +], function (FauxtonAPI, ReactComponents, utils, React) { + + var assert = utils.assert; + var TestUtils = React.addons.TestUtils; + var code = 'function (doc) {\n emit(doc._id, 1);\n}'; + + describe('CodeEditorPanel', function () { + + describe('Doc icon', function () { + it('hidden by default', function () { + var container = document.createElement('div'); + var codeEditorEl = TestUtils.renderIntoDocument( + <ReactComponents.CodeEditorPanel defaultCode={code} />, + container + ); + assert.equal($(codeEditorEl.getDOMNode()).find('.icon-question-sign').length, 0); + }); + it('hidden by default', function () { + var container = document.createElement('div'); + var codeEditorEl = TestUtils.renderIntoDocument( + <ReactComponents.CodeEditorPanel defaultCode={code} docLink="http://link.com" />, + container + ); + assert.equal($(codeEditorEl.getDOMNode()).find('.icon-question-sign').length, 1); + }); + }); + + describe('Zen Mode', function () { + it('shows zen mode by default', function () { + var container = document.createElement('div'); + var codeEditorEl = TestUtils.renderIntoDocument( + <ReactComponents.CodeEditorPanel defaultCode={code} />, + container + ); + assert.equal($(codeEditorEl.getDOMNode()).find('.zen-editor-icon').length, 1); + }); + + it('omits zen mode if explicitly turned off', function () { + var container = document.createElement('div'); + var codeEditorEl = TestUtils.renderIntoDocument( + <ReactComponents.CodeEditor defaultCode={code} allowZenMode={false} />, + container + ); + assert.equal($(codeEditorEl.getDOMNode()).find('.zen-editor-icon').length, 0); + }); + }); + + }); + +}); http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/f5f375c5/app/addons/components/tests/codeEditorSpec.js ---------------------------------------------------------------------- diff --git a/app/addons/components/tests/codeEditorSpec.js b/app/addons/components/tests/codeEditorSpec.js deleted file mode 100644 index 3832cd4..0000000 --- a/app/addons/components/tests/codeEditorSpec.js +++ /dev/null @@ -1,134 +0,0 @@ -// Licensed under the Apache License, Version 2.0 (the "License"); you may not -// use this file except in compliance with the License. You may obtain a copy of -// the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -// License for the specific language governing permissions and limitations under -// the License. -define([ - 'api', - 'addons/components/react-components.react', - - 'testUtils', - 'react' -], function (FauxtonAPI, ReactComponents, utils, React) { - - var assert = utils.assert; - var TestUtils = React.addons.TestUtils; - var code = 'function (doc) {\n emit(doc._id, 1);\n}'; - var code2 = 'function (doc) {\n if(doc._id) { \n emit(doc._id, 2); \n } \n}'; - - describe('Code Editor', function () { - var container, codeEditorEl, spy; - - beforeEach(function () { - spy = sinon.spy(); - container = document.createElement('div'); - codeEditorEl = TestUtils.renderIntoDocument( - React.createElement(ReactComponents.CodeEditor, {code: code, change: spy}), - container - ); - }); - - afterEach(function () { - React.unmountComponentAtNode(container); - }); - - describe('Tracking edits', function () { - - it('no change on mount', function () { - assert.notOk(codeEditorEl.hasChanged()); - }); - - it('detects change on user input', function () { - codeEditorEl.editor.setValue(code2, -1); - - assert.ok(codeEditorEl.hasChanged()); - }); - - }); - - describe('onBlur', function () { - - it('calls changed function', function () { - codeEditorEl.editor._emit('blur'); - assert.ok(spy.calledOnce); - }); - - }); - - describe('setHeightToLineCount', function () { - - beforeEach(function () { - codeEditorEl = TestUtils.renderIntoDocument( - React.createElement(ReactComponents.CodeEditor, {code: code, isFullPageEditor: false, setHeightWithJS: true}), - container - ); - - }); - - it('sets line height correctly for non full page', function () { - var spy = sinon.spy(codeEditorEl.editor, 'setOptions'); - - codeEditorEl.setHeightToLineCount(); - assert.ok(spy.calledOnce); - assert.equal(spy.getCall(0).args[0].maxLines, 3); - }); - - }); - - describe('removeIncorrectAnnotations', function () { - - beforeEach(function () { - codeEditorEl = TestUtils.renderIntoDocument( - React.createElement(ReactComponents.CodeEditor, {code: code}), - container - ); - - }); - - it('removes default errors that do not apply to CouchDB Views', function () { - assert.equal(codeEditorEl.getAnnotations(), 0); - }); - - }); - - describe('setEditorValue', function () { - - it('sets new code', function () { - codeEditorEl = TestUtils.renderIntoDocument( - React.createElement(ReactComponents.CodeEditor, {code: code}), - container - ); - - codeEditorEl.setEditorValue(code2); - assert.deepEqual(codeEditorEl.getValue(), code2); - }); - - }); - - describe('showEditorOnly', function () { - - it('only shows editor when showEditorOnly=true', function () { - codeEditorEl = TestUtils.renderIntoDocument( - React.createElement(ReactComponents.CodeEditor, {code: code, showEditorOnly: true}), - container - ); - assert.notOk($(codeEditorEl.getDOMNode()).hasClass('control-group')); - }); - - it('shows everything by default', function () { - var codeEditorEl = TestUtils.renderIntoDocument( - React.createElement(ReactComponents.CodeEditor, {code: code}), - container - ); - assert.ok($(codeEditorEl.getDOMNode()).hasClass('control-group')); - }); - - }); - }); -}); http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/f5f375c5/app/addons/components/tests/codeEditorSpec.react.jsx ---------------------------------------------------------------------- diff --git a/app/addons/components/tests/codeEditorSpec.react.jsx b/app/addons/components/tests/codeEditorSpec.react.jsx index 3d77c89..4f1fcaf 100644 --- a/app/addons/components/tests/codeEditorSpec.react.jsx +++ b/app/addons/components/tests/codeEditorSpec.react.jsx @@ -21,6 +21,11 @@ define([ var code = 'function (doc) {\n emit(doc._id, 1);\n}'; var code2 = 'function (doc) {\n if(doc._id) { \n emit(doc._id, 2); \n } \n}'; + var ignorableErrors = [ + 'Missing name in function declaration.', + "['{a}'] is better written in dot notation." + ]; + describe('Code Editor', function () { var container, codeEditorEl, spy; @@ -28,7 +33,7 @@ define([ spy = sinon.spy(); container = document.createElement('div'); codeEditorEl = TestUtils.renderIntoDocument( - <ReactComponents.CodeEditor code={code} change={spy} />, + <ReactComponents.CodeEditor defaultCode={code} blur={spy} />, container ); }); @@ -38,124 +43,58 @@ define([ }); describe('Tracking edits', function () { - it('no change on mount', function () { assert.notOk(codeEditorEl.hasChanged()); }); it('detects change on user input', function () { codeEditorEl.editor.setValue(code2, -1); - assert.ok(codeEditorEl.hasChanged()); }); - }); describe('onBlur', function () { - - it('calls changed function', function () { + it('calls blur function', function () { codeEditorEl.editor._emit('blur'); assert.ok(spy.calledOnce); }); - }); describe('setHeightToLineCount', function () { - - beforeEach(function () { + it('check default num lines #1', function () { codeEditorEl = TestUtils.renderIntoDocument( - <ReactComponents.CodeEditor code={code} isFullPageEditor={false} setHeightWithJS={true}/>, + <ReactComponents.CodeEditor code={code} setHeightToLineCount={true} />, container ); - - }); - - it('sets line height correctly for non full page', function () { - var spy = sinon.spy(codeEditorEl.editor, 'setOptions'); - - codeEditorEl.setHeightToLineCount(); - assert.ok(spy.calledOnce); - assert.equal(spy.getCall(0).args[0].maxLines, 3); + assert.ok(codeEditorEl.editor.getSession().getDocument().getLength(), 3); }); - - }); - - describe('removeIncorrectAnnotations', function () { - - beforeEach(function () { + it('check default num lines #2', function () { codeEditorEl = TestUtils.renderIntoDocument( - <ReactComponents.CodeEditor code={code}/>, + <ReactComponents.CodeEditor code={code2} setHeightToLineCount={true} />, container ); - + assert.ok(codeEditorEl.editor.getSession().getDocument().getLength(), 5); }); - - it('removes default errors that do not apply to CouchDB Views', function () { - assert.equal(codeEditorEl.getAnnotations(), 0); - }); - - }); - - describe('setEditorValue', function () { - - it('sets new code', function () { + it('check maxLines', function () { codeEditorEl = TestUtils.renderIntoDocument( - <ReactComponents.CodeEditor code={code}/>, + <ReactComponents.CodeEditor code={code2} setHeightToLineCount={true} maxLines={2} />, container ); - - codeEditorEl.setEditorValue(code2); - assert.deepEqual(codeEditorEl.getValue(), code2); + assert.ok(codeEditorEl.editor.getSession().getDocument().getLength(), 2); }); - }); - describe('showEditorOnly', function () { - - it('only shows editor when showEditorOnly=true', function () { + describe('removeIncorrectAnnotations', function () { + beforeEach(function () { codeEditorEl = TestUtils.renderIntoDocument( - <ReactComponents.CodeEditor code={code} showEditorOnly={true} />, + <ReactComponents.CodeEditor defaultCode={code} ignorableErrors={ignorableErrors} />, container ); - assert.notOk($(codeEditorEl.getDOMNode()).hasClass('control-group')); }); - - it('shows everything by default', function () { - var codeEditorEl = TestUtils.renderIntoDocument( - <ReactComponents.CodeEditor code={code} />, - container - ); - assert.ok($(codeEditorEl.getDOMNode()).hasClass('control-group')); + it('removes default errors that do not apply to CouchDB Views', function () { + assert.equal(codeEditorEl.getAnnotations(), 0); }); - }); - describe('Zen Mode', function () { - it('shows zen mode by default', function () { - var container = document.createElement('div'); - var codeEditorEl = TestUtils.renderIntoDocument( - <ReactComponents.CodeEditor code={code} change={spy} docs="http://link.com" />, - container - ); - assert.equal($(codeEditorEl.getDOMNode()).find('.zen-editor-icon').length, 1); - }); - - it('omits zen mode if explicitly turned off', function () { - var container = document.createElement('div'); - var codeEditorEl = TestUtils.renderIntoDocument( - <ReactComponents.CodeEditor code={code} change={spy} docs="http://link.com" allowZenMode={false} />, - container - ); - assert.equal($(codeEditorEl.getDOMNode()).find('.zen-editor-icon').length, 0); - }); - - it('updates parent editor after changing content in zen mode', function () { - var container = document.createElement('div'); - var codeEditorEl = TestUtils.renderIntoDocument( - <ReactComponents.CodeEditor code={code} change={spy} docs="http://link.com" />, - container - ); - }); - }); }); }); http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/f5f375c5/app/addons/components/tests/zenModeSpec.react.jsx ---------------------------------------------------------------------- diff --git a/app/addons/components/tests/zenModeSpec.react.jsx b/app/addons/components/tests/zenModeSpec.react.jsx index 5e51e1d..ec742d4 100644 --- a/app/addons/components/tests/zenModeSpec.react.jsx +++ b/app/addons/components/tests/zenModeSpec.react.jsx @@ -34,6 +34,7 @@ define([ afterEach(function () { React.unmountComponentAtNode(container); + window.localStorage.removeItem('zenTheme'); }); describe('Toggle theme', function () { @@ -44,11 +45,13 @@ define([ it('switch to light theme on click', function () { TestUtils.Simulate.click($(el.getDOMNode()).find('.js-toggle-theme')[0]); assert.ok($(el.getDOMNode()).hasClass('zen-theme-light')); + // reset + TestUtils.Simulate.click($(el.getDOMNode()).find('.js-toggle-theme')[0]); }); }); describe('Closing zen mode', function () { - it('defaults to dark theme', function () { + it('method called', function () { TestUtils.Simulate.click($(el.getDOMNode()).find('.js-exit-zen-mode')[0]); assert.ok(spy.calledOnce); }); http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/f5f375c5/app/addons/documents/index-editor/components.react.jsx ---------------------------------------------------------------------- diff --git a/app/addons/documents/index-editor/components.react.jsx b/app/addons/documents/index-editor/components.react.jsx index 2450f62..719bf56 100644 --- a/app/addons/documents/index-editor/components.react.jsx +++ b/app/addons/documents/index-editor/components.react.jsx @@ -24,7 +24,7 @@ function (app, FauxtonAPI, React, Stores, Actions, Components, ReactComponents) var indexEditorStore = Stores.indexEditorStore; var getDocUrl = app.helpers.getDocUrl; var StyledSelect = ReactComponents.StyledSelect; - var CodeEditor = ReactComponents.CodeEditor; + var CodeEditorPanel = ReactComponents.CodeEditorPanel; var PaddedBorderedBox = ReactComponents.PaddedBorderedBox; var ConfirmButton = ReactComponents.ConfirmButton; var LoadLines = ReactComponents.LoadLines; @@ -178,12 +178,13 @@ function (app, FauxtonAPI, React, Stores, Actions, Components, ReactComponents) customReduceSection; if (this.state.hasCustomReduce) { - customReduceSection = <CodeEditor + customReduceSection = <CodeEditorPanel ref='reduceEditor' id='reduce-function' - code={this.state.reduce} - change={this.updateReduceCode} - docs={false} title={'Custom Reduce function'} />; + title={'Custom Reduce function'} + defaultCode={this.state.reduce} + blur={this.updateReduceCode} + />; } return ( @@ -319,16 +320,6 @@ function (app, FauxtonAPI, React, Stores, Actions, Components, ReactComponents) }, this); }, - clearNotifications: function () { - ['mapEditor', 'reduceEditor'].forEach(function (editorName) { - if (editorName === 'reduceEditor' && !indexEditorStore.hasCustomReduce()) { - return; - } - var editor = this.refs[editorName].getEditor(); - editor.editSaved(); - }.bind(this)); - }, - saveView: function (event) { event.preventDefault(); @@ -341,8 +332,6 @@ function (app, FauxtonAPI, React, Stores, Actions, Components, ReactComponents) return; } - this.clearNotifications(); - Actions.saveView({ database: this.state.database, newView: this.state.isNewView, @@ -423,13 +412,13 @@ function (app, FauxtonAPI, React, Stores, Actions, Components, ReactComponents) </div> <div className="control-group"> <PaddedBorderedBox> - <CodeEditor + <CodeEditorPanel id={'map-function'} ref="mapEditor" title={"Map function"} - docs={getDocUrl('MAP_FUNCS')} - change={this.updateMapCode} - code={this.state.map} /> + docLink={getDocUrl('MAP_FUNCS')} + blur={this.updateMapCode} + defaultCode={this.state.map} /> </PaddedBorderedBox> </div> <PaddedBorderedBox> http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/f5f375c5/app/addons/documents/index-editor/tests/viewIndex.componentsSpec.react.jsx ---------------------------------------------------------------------- diff --git a/app/addons/documents/index-editor/tests/viewIndex.componentsSpec.react.jsx b/app/addons/documents/index-editor/tests/viewIndex.componentsSpec.react.jsx index 5fea728..4423572 100644 --- a/app/addons/documents/index-editor/tests/viewIndex.componentsSpec.react.jsx +++ b/app/addons/documents/index-editor/tests/viewIndex.componentsSpec.react.jsx @@ -221,7 +221,7 @@ define([ }); describe('Editor', function () { - var container, editorEl, reduceStub; + var container, editorEl; beforeEach(function () { container = document.createElement('div'); @@ -235,13 +235,13 @@ define([ }); it('returns false on invalid map editor code', function () { - var stub = sinon.stub(editorEl.refs.mapEditor, 'hadValidCode'); + var stub = sinon.stub(editorEl.refs.mapEditor.getEditor(), 'hadValidCode'); stub.returns(false); assert.notOk(editorEl.hasValidCode()); }); it('returns true on valid map editor code', function () { - var stub = sinon.stub(editorEl.refs.mapEditor, 'hadValidCode'); + var stub = sinon.stub(editorEl.refs.mapEditor.getEditor(), 'hadValidCode'); stub.returns(true); assert.ok(editorEl.hasValidCode()); }); http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/f5f375c5/app/addons/documents/mango/mango.components.react.jsx ---------------------------------------------------------------------- diff --git a/app/addons/documents/mango/mango.components.react.jsx b/app/addons/documents/mango/mango.components.react.jsx index 6dba412..303fbaa 100644 --- a/app/addons/documents/mango/mango.components.react.jsx +++ b/app/addons/documents/mango/mango.components.react.jsx @@ -30,7 +30,7 @@ function (app, FauxtonAPI, React, Stores, Actions, var getDocUrl = app.helpers.getDocUrl; var PaddedBorderedBox = ReactComponents.PaddedBorderedBox; - var CodeEditor = ReactComponents.CodeEditor; + var CodeEditorPanel = ReactComponents.CodeEditorPanel; var ConfirmButton = ReactComponents.ConfirmButton; var MangoQueryEditorController = React.createClass({ @@ -45,7 +45,7 @@ function (app, FauxtonAPI, React, Stores, Actions, changedQuery: mangoStore.getQueryFindCodeChanged(), availableIndexes: mangoStore.getAvailableQueryIndexes(), additionalIndexes: mangoStore.getAvailableAdditionalIndexes(), - isLoading: mangoStore.getLoadingIndexes(), + isLoading: mangoStore.getLoadingIndexes() }; }, @@ -144,13 +144,12 @@ function (app, FauxtonAPI, React, Stores, Actions, </PaddedBorderedBox> <form className="form-horizontal" onSubmit={this.props.onSubmit}> <PaddedBorderedBox> - <CodeEditor + <CodeEditorPanel id="query-field" ref="field" title={this.props.title} - docs={this.props.docs} - code={this.props.exampleCode} - disableUnload={true} /> + docLink={this.props.docs} + defaultCode={this.props.exampleCode} /> {this.getChangedQueryText()} </PaddedBorderedBox> {this.getIndexBox()}
