You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by ga...@apache.org on 2017/08/14 07:59:16 UTC
[couchdb-fauxton] branch master updated: Fix sidebar
expand/collapse when design doc name has special chars (#948)
This is an automated email from the ASF dual-hosted git repository.
garren pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/couchdb-fauxton.git
The following commit(s) were added to refs/heads/master by this push:
new fad82ab Fix sidebar expand/collapse when design doc name has special chars (#948)
fad82ab is described below
commit fad82abcf5a31b9e4d14d27b1cb8b83602fdbc26
Author: Antonio Maranhao <30...@users.noreply.github.com>
AuthorDate: Mon Aug 14 03:59:14 2017 -0400
Fix sidebar expand/collapse when design doc name has special chars (#948)
Fix for sidebar when a design doc's name contains special characters
---
app/addons/documents/routes-index-editor.js | 5 +-
.../sidebar/__tests__/sidebar.components.test.js | 76 ++++++++++++++++++++++
app/addons/documents/sidebar/sidebar.js | 18 ++---
.../tests/nightwatch/checkSidebarBehavior.js | 29 +++++----
app/helpers.js | 4 ++
.../custom-commands/createDocument.js | 6 +-
test/nightwatch_tests/custom-commands/helper.js | 2 +-
7 files changed, 115 insertions(+), 25 deletions(-)
diff --git a/app/addons/documents/routes-index-editor.js b/app/addons/documents/routes-index-editor.js
index ea2fdf6..3c6fbd3 100644
--- a/app/addons/documents/routes-index-editor.js
+++ b/app/addons/documents/routes-index-editor.js
@@ -53,7 +53,6 @@ const IndexEditorAndResults = BaseRoute.extend({
const params = this.createParams(),
urlParams = params.urlParams,
docParams = params.docParams,
- decodeDdoc = decodeURIComponent(ddoc),
store = IndexResultsStores.indexResultsStore;
// if the user is simply switching the layout style (i.e. metadata, json, or table),
@@ -68,7 +67,7 @@ const IndexEditorAndResults = BaseRoute.extend({
viewName = viewName.replace(/\?.*$/, '');
this.indexedDocs = new Documents.IndexCollection(null, {
database: this.database,
- design: decodeDdoc,
+ design: ddoc,
view: viewName,
params: docParams,
paging: {
@@ -89,7 +88,7 @@ const IndexEditorAndResults = BaseRoute.extend({
newView: false,
database: this.database,
designDocs: this.designDocs,
- designDocId: '_design/' + decodeDdoc
+ designDocId: '_design/' + ddoc
});
SidebarActions.selectNavItem('designDoc', {
diff --git a/app/addons/documents/sidebar/__tests__/sidebar.components.test.js b/app/addons/documents/sidebar/__tests__/sidebar.components.test.js
new file mode 100644
index 0000000..f7fc09e
--- /dev/null
+++ b/app/addons/documents/sidebar/__tests__/sidebar.components.test.js
@@ -0,0 +1,76 @@
+// 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.
+
+import utils from '../../../../../test/mocha/testUtils';
+import FauxtonAPI from "../../../../core/api";
+import React from "react";
+import ReactDOM from "react-dom";
+import sinon from "sinon";
+import { mount } from 'enzyme';
+import Components from "../sidebar";
+
+const { assert, restore} = utils;
+
+
+describe('DesignDoc', () => {
+ const database = { id: 'test-db' };
+ const selectedNavInfo = {
+ navItem: 'all-docs',
+ designDocName: '',
+ designDocSection: '',
+ indexName: ''
+ };
+
+ afterEach(function () {
+ restore(FauxtonAPI.urls);
+ });
+
+ it('confirm URLs are properly encoded when design doc name has special chars', function () {
+ sinon.stub(FauxtonAPI, 'urls').callsFake((a, b, c, d) => {
+ if (a === 'designDocs') {
+ return '#/database/MOCK/_design/' + encodeURIComponent(c) + '/' + encodeURIComponent(d);
+ }
+ return '' + (a || '') + '/' + (b || '') + '/' + (c || '') + '/' + (d || '');
+ });
+ const wrapper = mount(<Components.DesignDoc
+ database={database}
+ toggle={sinon.stub()}
+ sidebarListTypes={[]}
+ isExpanded={true}
+ designDocName={'doc-$-#-.1'}
+ selectedNavInfo={selectedNavInfo}
+ toggledSections={{}}
+ designDoc={{}} />);
+
+ assert.include(wrapper.find('a.icon .fonticon-plus-circled').at(1).props()['href'], '/doc-%24-%23-.1');
+ assert.include(wrapper.find('a.toggle-view .accordion-header').props()['href'], '/doc-%24-%23-.1');
+ });
+
+ it('check toggle() works when design doc name has special characters', function () {
+ sinon.stub(FauxtonAPI, 'urls');
+
+ const toggleStub = sinon.stub();
+ const wrapper = mount(<Components.DesignDoc
+ database={database}
+ toggle={toggleStub}
+ sidebarListTypes={[]}
+ isExpanded={true}
+ designDocName={'id#1'}
+ selectedNavInfo={{}}
+ toggledSections={{}}
+ designDoc={{}} />);
+
+ // NOTE: wrapper.find doesn't work special chars so we use class name instead
+ wrapper.find('div.accordion-list-item').simulate('click', {preventDefault: sinon.stub()});
+ assert.ok(toggleStub.calledOnce);
+ });
+});
diff --git a/app/addons/documents/sidebar/sidebar.js b/app/addons/documents/sidebar/sidebar.js
index 9898fa1..79daa6e 100644
--- a/app/addons/documents/sidebar/sidebar.js
+++ b/app/addons/documents/sidebar/sidebar.js
@@ -11,6 +11,7 @@
// the License.
import app from "../../../app";
+import Helpers from "../../../helpers";
import FauxtonAPI from "../../../core/api";
import React from "react";
import ReactDOM from "react-dom";
@@ -148,14 +149,14 @@ var IndexSection = React.createClass({
var sortedItems = this.props.items.sort();
return _.map(sortedItems, function (indexName, index) {
- var href = FauxtonAPI.urls(this.props.urlNamespace, 'app', encodeURIComponent(this.props.database.id), this.props.designDocName);
+ var href = FauxtonAPI.urls(this.props.urlNamespace, 'app', encodeURIComponent(this.props.database.id), encodeURIComponent(this.props.designDocName));
var className = (this.props.selectedIndex === indexName) ? 'active' : '';
return (
<li className={className} key={index}>
<a
id={this.props.designDocName + '_' + indexName}
- href={"#/" + href + indexName}
+ href={"#/" + href + encodeURIComponent(indexName)}
className="toggle-view">
{indexName}
</a>
@@ -257,7 +258,8 @@ var DesignDoc = React.createClass({
sidebarListTypes: React.PropTypes.array.isRequired,
isExpanded: React.PropTypes.bool.isRequired,
selectedNavInfo: React.PropTypes.object.isRequired,
- toggledSections: React.PropTypes.object.isRequired
+ toggledSections: React.PropTypes.object.isRequired,
+ designDocName: React.PropTypes.string.isRequired
},
getInitialState: function () {
@@ -319,7 +321,7 @@ var DesignDoc = React.createClass({
e.preventDefault();
var newToggleState = !this.props.isExpanded;
var state = newToggleState ? 'show' : 'hide';
- $(ReactDOM.findDOMNode(this)).find('#' + this.props.designDocName).collapse(state);
+ $(ReactDOM.findDOMNode(this)).find('#' + Helpers.escapeJQuerySelector(this.props.designDocName)).collapse(state);
this.props.toggle(this.props.designDocName);
},
@@ -330,13 +332,13 @@ var DesignDoc = React.createClass({
var addNewLinks = _.reduce(FauxtonAPI.getExtensions('sidebar:links'), function (menuLinks, link) {
menuLinks.push({
title: link.title,
- url: '#' + newUrlPrefix + '/' + link.url + '/' + designDocName,
+ url: '#' + newUrlPrefix + '/' + link.url + '/' + encodeURIComponent(designDocName),
icon: 'fonticon-plus-circled'
});
return menuLinks;
}, [{
title: 'New View',
- url: '#' + FauxtonAPI.urls('new', 'addView', encodeURIComponent(this.props.database.id), designDocName),
+ url: '#' + FauxtonAPI.urls('new', 'addView', encodeURIComponent(this.props.database.id), encodeURIComponent(designDocName)),
icon: 'fonticon-plus-circled'
}]);
@@ -356,7 +358,7 @@ var DesignDoc = React.createClass({
toggleBodyClassNames += ' in';
}
var designDocName = this.props.designDocName;
- var designDocMetaUrl = FauxtonAPI.urls('designDocs', 'app', encodeURIComponent(this.props.database.id), designDocName);
+ var designDocMetaUrl = FauxtonAPI.urls('designDocs', 'app', this.props.database.id, designDocName);
var metadataRowClass = (this.props.selectedNavInfo.designDocSection === 'metadata') ? 'active' : '';
return (
@@ -394,7 +396,7 @@ var DesignDocList = React.createClass({
designDocList: function () {
return _.map(this.props.designDocs, function (designDoc, key) {
- var ddName = designDoc.safeId;
+ var ddName = decodeURIComponent(designDoc.safeId);
// only pass down the selected nav info and toggle info if they're relevant for this particular design doc
var expanded = false,
diff --git a/app/addons/documents/tests/nightwatch/checkSidebarBehavior.js b/app/addons/documents/tests/nightwatch/checkSidebarBehavior.js
index 4329c73..680eee7 100644
--- a/app/addons/documents/tests/nightwatch/checkSidebarBehavior.js
+++ b/app/addons/documents/tests/nightwatch/checkSidebarBehavior.js
@@ -14,24 +14,29 @@
module.exports = {
- 'Checks if design docs that have a dot symbol in the id show up in the UI': function (client) {
- var waitTime = 10000,
+ 'Checks if design docs that have special chars in the ID show up in the UI and are clickable': function (client) {
+ const waitTime = 10000,
newDatabaseName = client.globals.testDatabaseName,
baseUrl = client.globals.test_settings.launch_url;
-
+ const docNormal = 'ddoc_normal';
+ const docSpecialChars = 'ddoc_with.$pecialcharacters()+-';
+ const docSpecialCharsEncoded = 'ddoc_with.%24pecialcharacters()%2B-';
client
.loginToGUI()
- .createDocument('_design/ddoc_normal', newDatabaseName)
- .createDocument('_design/ddoc.with.specialcharacters', newDatabaseName)
+ .createDocument('_design/' + docNormal, newDatabaseName)
+ .createDocument('_design/' + docSpecialChars, newDatabaseName)
.url(baseUrl + '/#/database/' + newDatabaseName + '/_all_docs')
.waitForElementPresent('.nav-list', waitTime, false)
- .assert.hidden('a[href="#/database/' + newDatabaseName + '/_design/ddoc_normal/_info"]')
- .assert.hidden('a[href="#/database/' + newDatabaseName + '/_design/ddoc.with.specialcharacters/_info"]')
-
- .clickWhenVisible('#nav-header-ddoc_normal')
- .assert.visible('a[href="#/database/' + newDatabaseName + '/_design/ddoc_normal/_info"]')
- .clickWhenVisible('[title="_design/ddoc.with.specialcharacters"]')
- .assert.visible('a[href="#/database/' + newDatabaseName + '/_design/ddoc.with.specialcharacters/_info"]')
+ // Verify 'Metadata' subitem is not visible
+ .assert.hidden('a[href="#/database/' + newDatabaseName + '/_design/' + docNormal + '/_info"]')
+ .assert.hidden('a[href="#/database/' + newDatabaseName + '/_design/' + docSpecialCharsEncoded + '/_info"]')
+ // Click sidebar items and verify they expand
+ .clickWhenVisible('#nav-header-' + docNormal)
+ .assert.visible('a[href="#/database/' + newDatabaseName + '/_design/' + docNormal + '/_info"]')
+ .clickWhenVisible('span[title="_design/' + docSpecialChars + '"]')
+ .assert.visible('a[href="#/database/' + newDatabaseName + '/_design/' + docSpecialCharsEncoded + '/_info"]')
+ // Verify display name is not encoded
+ .assert.containsText('span[title="_design/' + docSpecialChars + '"]', docSpecialChars)
.end();
}
};
diff --git a/app/helpers.js b/app/helpers.js
index 2991bcf..f242722 100644
--- a/app/helpers.js
+++ b/app/helpers.js
@@ -57,4 +57,8 @@ Helpers.getDateFromNow = function (timestamp) {
return moment(timestamp, 'X').fromNow();
};
+Helpers.escapeJQuerySelector = function (selector) {
+ return selector && selector.replace(/[!"#$%&'()*+,.\/:;<=>?@[\\\]^`{|}~]/g, "\\$&");
+};
+
export default Helpers;
diff --git a/test/nightwatch_tests/custom-commands/createDocument.js b/test/nightwatch_tests/custom-commands/createDocument.js
index ffa9fa6..bd39afb 100644
--- a/test/nightwatch_tests/custom-commands/createDocument.js
+++ b/test/nightwatch_tests/custom-commands/createDocument.js
@@ -47,7 +47,11 @@ CreateDocument.prototype.command = function (documentName, databaseName, docCont
if (!databaseName) {
databaseName = helpers.testDatabaseName;
}
-
+ if (documentName.startsWith('_design/')) {
+ documentName = '_design/' + encodeURIComponent(documentName.substr(8));
+ } else {
+ documentName = encodeURIComponent(documentName);
+ }
const url = [couchUrl, databaseName, documentName].join('/');
checkForDocumentCreated(url, helpers.maxWaitTime, () => {
diff --git a/test/nightwatch_tests/custom-commands/helper.js b/test/nightwatch_tests/custom-commands/helper.js
index 93a1516..64f3abc 100644
--- a/test/nightwatch_tests/custom-commands/helper.js
+++ b/test/nightwatch_tests/custom-commands/helper.js
@@ -3,7 +3,7 @@ const request = require('request');
exports.checkForDocumentCreated = function checkForDocumentCreated (url, timeout, cb) {
const timeOutId = setTimeout(() => {
- throw new Error('timeout waiting for doc to appear');
+ throw new Error('timeout waiting for doc to appear (' + url + ')');
}, timeout);
const intervalId = setInterval(() => {
--
To stop receiving notification emails like this one, please contact
['"commits@couchdb.apache.org" <co...@couchdb.apache.org>'].