summaryrefslogtreecommitdiffstats
path: root/core/js
diff options
context:
space:
mode:
Diffstat (limited to 'core/js')
-rw-r--r--core/js/contactsmenu.js523
-rw-r--r--core/js/core.json1
-rw-r--r--core/js/js.js78
-rw-r--r--core/js/login/authpicker.js13
-rw-r--r--core/js/login/redirect.js3
-rw-r--r--core/js/sharedialoglinkshareview.js8
-rw-r--r--core/js/sharedialogview.js81
-rw-r--r--core/js/shareitemmodel.js62
-rw-r--r--core/js/tests/specs/contactsmenuSpec.js265
-rw-r--r--core/js/tests/specs/jquery.avatarSpec.js2
-rw-r--r--core/js/tests/specs/sharedialoglinkshareview.js143
-rw-r--r--core/js/tests/specs/sharedialogviewSpec.js4
-rw-r--r--core/js/tests/specs/shareitemmodelSpec.js153
13 files changed, 1252 insertions, 84 deletions
diff --git a/core/js/contactsmenu.js b/core/js/contactsmenu.js
new file mode 100644
index 00000000000..15c48887d20
--- /dev/null
+++ b/core/js/contactsmenu.js
@@ -0,0 +1,523 @@
+/* global OC.Backbone, Handlebars, Promise, _ */
+
+/**
+ * @copyright 2017 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2017 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+(function(OC, $, _, Handlebars) {
+ 'use strict';
+
+ var MENU_TEMPLATE = ''
+ + '<input id="contactsmenu-search" type="search" placeholder="Search contacts …" value="{{searchTerm}}">'
+ + '<div class="content">'
+ + '</div>';
+ var CONTACTS_LIST_TEMPLATE = ''
+ + '{{#unless contacts.length}}'
+ + '<div class="emptycontent">'
+ + ' <div class="icon-search"></div>'
+ + ' <h2>' + t('core', 'No contacts found') + '</h2>'
+ + '</div>'
+ + '{{/unless}}'
+ + '<div id="contactsmenu-contacts"></div>'
+ + '{{#if contactsAppEnabled}}<div class="footer"><a href="{{contactsAppURL}}">' + t('core', 'Show all contacts …') + '</a></div>{{/if}}';
+ var LOADING_TEMPLATE = ''
+ + '<div class="emptycontent">'
+ + ' <div class="icon-loading"></div>'
+ + ' <h2>{{loadingText}}</h2>'
+ + '</div>';
+ var ERROR_TEMPLATE = ''
+ + '<div class="emptycontent">'
+ + ' <div class="icon-search"></div>'
+ + ' <h2>' + t('core', 'There was an error loading your contacts') + '</h2>'
+ + '</div>';
+ var CONTACT_TEMPLATE = ''
+ + '{{#if contact.avatar}}'
+ + '<img src="{{contact.avatar}}" class="avatar">'
+ + '{{else}}'
+ + '<div class="avatar"></div>'
+ + '{{/if}}'
+ + '<div class="body">'
+ + ' <div class="full-name">{{contact.fullName}}</div>'
+ + ' <div class="last-message">{{contact.lastMessage}}</div>'
+ + '</div>'
+ + '{{#if contact.topAction}}'
+ + '<a class="top-action" href="{{contact.topAction.hyperlink}}" title="{{contact.topAction.title}}">'
+ + ' <img src="{{contact.topAction.icon}}">'
+ + '</a>'
+ + '{{/if}}'
+ + '{{#if contact.hasTwoActions}}'
+ + '<a class="second-action" href="{{contact.secondAction.hyperlink}}">'
+ + ' <img src="{{contact.secondAction.icon}}">'
+ + '</a>'
+ + '{{/if}}'
+ + '{{#if contact.hasManyActions}}'
+ + ' <span class="other-actions icon-more"></span>'
+ + ' <div class="menu popovermenu">'
+ + ' <ul>'
+ + ' {{#each contact.actions}}'
+ + ' <li>'
+ + ' <a href="{{hyperlink}}">'
+ + ' <img src="{{icon}}">'
+ + ' <span>{{title}}</span>'
+ + ' </a>'
+ + ' </li>'
+ + ' {{/each}}'
+ + ' </ul>'
+ + ' </div>'
+ + '{{/if}}';
+
+ /**
+ * @class Contact
+ */
+ var Contact = OC.Backbone.Model.extend({
+ defaults: {
+ fullName: '',
+ lastMessage: '',
+ actions: [],
+ hasOneAction: false,
+ hasTwoActions: false,
+ hasManyActions: false
+ },
+
+ /**
+ * @returns {undefined}
+ */
+ initialize: function() {
+ // Add needed property for easier template rendering
+ if (this.get('actions').length === 0) {
+ this.set('hasOneAction', true);
+ } else if (this.get('actions').length === 1) {
+ this.set('hasTwoActions', true);
+ this.set('secondAction', this.get('actions')[0]);
+ } else {
+ this.set('hasManyActions', true);
+ }
+ }
+ });
+
+ /**
+ * @class ContactCollection
+ */
+ var ContactCollection = OC.Backbone.Collection.extend({
+ model: Contact
+ });
+
+ /**
+ * @class ContactsListView
+ */
+ var ContactsListView = OC.Backbone.View.extend({
+
+ /** @type {ContactsCollection} */
+ _collection: undefined,
+
+ /** @type {array} */
+ _subViews: [],
+
+ /**
+ * @param {object} options
+ * @returns {undefined}
+ */
+ initialize: function(options) {
+ this._collection = options.collection;
+ },
+
+ /**
+ * @returns {self}
+ */
+ render: function() {
+ var self = this;
+ self.$el.html('');
+ self._subViews = [];
+
+ self._collection.forEach(function(contact) {
+ var item = new ContactsListItemView({
+ model: contact
+ });
+ item.render();
+ self.$el.append(item.$el);
+ item.on('toggle:actionmenu', self._onChildActionMenuToggle, self);
+ self._subViews.push(item);
+ });
+
+ return self;
+ },
+
+ /**
+ * Event callback to propagate opening (another) entry's action menu
+ *
+ * @param {type} $src
+ * @returns {undefined}
+ */
+ _onChildActionMenuToggle: function($src) {
+ this._subViews.forEach(function(view) {
+ view.trigger('parent:toggle:actionmenu', $src);
+ });
+ }
+ });
+
+ /**
+ * @class CotnactsListItemView
+ */
+ var ContactsListItemView = OC.Backbone.View.extend({
+
+ /** @type {string} */
+ className: 'contact',
+
+ /** @type {undefined|function} */
+ _template: undefined,
+
+ /** @type {Contact} */
+ _model: undefined,
+
+ /** @type {boolean} */
+ _actionMenuShown: false,
+
+ events: {
+ 'click .icon-more': '_onToggleActionsMenu'
+ },
+
+ /**
+ * @param {object} data
+ * @returns {undefined}
+ */
+ template: function(data) {
+ if (!this._template) {
+ this._template = Handlebars.compile(CONTACT_TEMPLATE);
+ }
+ return this._template(data);
+ },
+
+ /**
+ * @param {object} options
+ * @returns {undefined}
+ */
+ initialize: function(options) {
+ this._model = options.model;
+ this.on('parent:toggle:actionmenu', this._onOtherActionMenuOpened, this);
+ },
+
+ /**
+ * @returns {self}
+ */
+ render: function() {
+ this.$el.html(this.template({
+ contact: this._model.toJSON()
+ }));
+ this.delegateEvents();
+
+ // Show placeholder iff no avatar is available (avatar is rendered as img, not div)
+ this.$('div.avatar').imageplaceholder(this._model.get('fullName'));
+
+ // Show tooltip for top action
+ this.$('.top-action').tooltip({placement: 'left'});
+
+ return this;
+ },
+
+ /**
+ * Toggle the visibility of the action popover menu
+ *
+ * @private
+ * @returns {undefined}
+ */
+ _onToggleActionsMenu: function() {
+ this._actionMenuShown = !this._actionMenuShown;
+ if (this._actionMenuShown) {
+ this.$('.menu').show();
+ } else {
+ this.$('.menu').hide();
+ }
+ this.trigger('toggle:actionmenu', this.$el);
+ },
+
+ /**
+ * @private
+ * @argument {jQuery} $src
+ * @returns {undefined}
+ */
+ _onOtherActionMenuOpened: function($src) {
+ if (this.$el.is($src)) {
+ // Ignore
+ return;
+ }
+ this._actionMenuShown = false;
+ this.$('.menu').hide();
+ }
+ });
+
+ /**
+ * @class ContactsMenuView
+ */
+ var ContactsMenuView = OC.Backbone.View.extend({
+
+ /** @type {undefined|function} */
+ _loadingTemplate: undefined,
+
+ /** @type {undefined|function} */
+ _errorTemplate: undefined,
+
+ /** @type {undefined|function} */
+ _contentTemplate: undefined,
+
+ /** @type {undefined|function} */
+ _contactsTemplate: undefined,
+
+ /** @type {undefined|ContactCollection} */
+ _contacts: undefined,
+
+ events: {
+ 'input #contactsmenu-search': '_onSearch'
+ },
+
+ /**
+ * @returns {undefined}
+ */
+ _onSearch: _.debounce(function() {
+ this.trigger('search', this.$('#contactsmenu-search').val());
+ }, 700),
+
+ /**
+ * @param {object} data
+ * @returns {string}
+ */
+ loadingTemplate: function(data) {
+ if (!this._loadingTemplate) {
+ this._loadingTemplate = Handlebars.compile(LOADING_TEMPLATE);
+ }
+ return this._loadingTemplate(data);
+ },
+
+ /**
+ * @param {object} data
+ * @returns {string}
+ */
+ errorTemplate: function(data) {
+ if (!this._errorTemplate) {
+ this._errorTemplate = Handlebars.compile(ERROR_TEMPLATE);
+ }
+ return this._errorTemplate(data);
+ },
+
+ /**
+ * @param {object} data
+ * @returns {string}
+ */
+ contentTemplate: function(data) {
+ if (!this._contentTemplate) {
+ this._contentTemplate = Handlebars.compile(MENU_TEMPLATE);
+ }
+ return this._contentTemplate(data);
+ },
+
+ /**
+ * @param {object} data
+ * @returns {string}
+ */
+ contactsTemplate: function(data) {
+ if (!this._contactsTemplate) {
+ this._contactsTemplate = Handlebars.compile(CONTACTS_LIST_TEMPLATE);
+ }
+ return this._contactsTemplate(data);
+ },
+
+ /**
+ * @param {object} options
+ * @returns {undefined}
+ */
+ initialize: function(options) {
+ this.options = options;
+ },
+
+ /**
+ * @param {string} text
+ * @returns {undefined}
+ */
+ showLoading: function(text) {
+ this.render();
+ this._contacts = undefined;
+ this.$('.content').html(this.loadingTemplate({
+ loadingText: text
+ }));
+ },
+
+ /**
+ * @returns {undefined}
+ */
+ showError: function() {
+ this.render();
+ this._contacts = undefined;
+ this.$('.content').html(this.errorTemplate());
+ },
+
+ /**
+ * @param {object} viewData
+ * @param {string} searchTerm
+ * @returns {undefined}
+ */
+ showContacts: function(viewData, searchTerm) {
+ this._contacts = viewData.contacts;
+ this.render({
+ contacts: viewData.contacts
+ });
+
+ var list = new ContactsListView({
+ collection: viewData.contacts
+ });
+ list.render();
+ this.$('.content').html(this.contactsTemplate({
+ contacts: viewData.contacts,
+ searchTerm: searchTerm,
+ contactsAppEnabled: viewData.contactsAppEnabled,
+ contactsAppURL: OC.generateUrl('/apps/contacts')
+ }));
+ this.$('#contactsmenu-contacts').html(list.$el);
+ },
+
+ /**
+ * @param {object} data
+ * @returns {self}
+ */
+ render: function(data) {
+ var searchVal = this.$('#contactsmenu-search').val();
+ this.$el.html(this.contentTemplate(data));
+
+ // Focus search
+ this.$('#contactsmenu-search').val(searchVal);
+ this.$('#contactsmenu-search').focus();
+ return this;
+ }
+
+ });
+
+ /**
+ * @param {Object} options
+ * @param {jQuery} options.el
+ * @param {jQuery} options.trigger
+ * @class ContactsMenu
+ */
+ var ContactsMenu = function(options) {
+ this.initialize(options);
+ };
+
+ ContactsMenu.prototype = {
+ /** @type {jQuery} */
+ $el: undefined,
+
+ /** @type {jQuery} */
+ _$trigger: undefined,
+
+ /** @type {ContactsMenuView} */
+ _view: undefined,
+
+ /** @type {Promise} */
+ _contactsPromise: undefined,
+
+ /**
+ * @param {Object} options
+ * @param {jQuery} options.el - the element to render the menu in
+ * @param {jQuery} options.trigger - the element to click on to open the menu
+ * @returns {undefined}
+ */
+ initialize: function(options) {
+ this.$el = options.el;
+ this._$trigger = options.trigger;
+
+ this._view = new ContactsMenuView({
+ el: this.$el
+ });
+ this._view.on('search', function(searchTerm) {
+ this._loadContacts(searchTerm);
+ }, this);
+
+ OC.registerMenu(this._$trigger, this.$el, function() {
+ this._toggleVisibility(true);
+ }.bind(this));
+ this.$el.on('beforeHide', function() {
+ this._toggleVisibility(false);
+ }.bind(this));
+ },
+
+ /**
+ * @private
+ * @param {boolean} show
+ * @returns {Promise}
+ */
+ _toggleVisibility: function(show) {
+ if (show) {
+ return this._loadContacts();
+ } else {
+ this.$el.html('');
+ return Promise.resolve();
+ }
+ },
+
+ /**
+ * @private
+ * @param {string|undefined} searchTerm
+ * @returns {Promise}
+ */
+ _getContacts: function(searchTerm) {
+ var url = OC.generateUrl('/contactsmenu/contacts');
+ return Promise.resolve($.ajax(url, {
+ method: 'POST',
+ data: {
+ filter: searchTerm
+ }
+ }));
+ },
+
+ /**
+ * @param {string|undefined} searchTerm
+ * @returns {undefined}
+ */
+ _loadContacts: function(searchTerm) {
+ var self = this;
+
+ if (!self._contactsPromise) {
+ self._contactsPromise = self._getContacts(searchTerm);
+ }
+
+ if (_.isUndefined(searchTerm) || searchTerm === '') {
+ self._view.showLoading(t('core', 'Loading your contacts …'));
+ } else {
+ self._view.showLoading(t('core', 'Looking for {term} …', {
+ term: searchTerm
+ }));
+ }
+ return self._contactsPromise.then(function(data) {
+ // Convert contact entries to Backbone collection
+ data.contacts = new ContactCollection(data.contacts);
+
+ self._view.showContacts(data, searchTerm);
+ }, function(e) {
+ self._view.showError();
+ console.error('There was an error loading your contacts', e);
+ }).then(function() {
+ // Delete promise, so that contacts are fetched again when the
+ // menu is opened the next time.
+ delete self._contactsPromise;
+ }).catch(console.error.bind(this));
+ }
+ };
+
+ OC.ContactsMenu = ContactsMenu;
+
+})(OC, $, _, Handlebars);
diff --git a/core/js/core.json b/core/js/core.json
index 6494d4105f8..aadd66a0558 100644
--- a/core/js/core.json
+++ b/core/js/core.json
@@ -40,6 +40,7 @@
"sharedialogresharerinfoview.js",
"sharedialogshareelistview.js",
"octemplate.js",
+ "contactsmenu.js",
"eventsource.js",
"config.js",
"public/appconfig.js",
diff --git a/core/js/js.js b/core/js/js.js
index 883431b2b02..d601f79033e 100644
--- a/core/js/js.js
+++ b/core/js/js.js
@@ -654,8 +654,13 @@ var OCP = {},
/**
* For menu toggling
* @todo Write documentation
+ *
+ * @param {jQuery} $toggle
+ * @param {jQuery} $menuEl
+ * @param {function|undefined} toggle callback invoked everytime the menu is opened
+ * @returns {undefined}
*/
- registerMenu: function($toggle, $menuEl) {
+ registerMenu: function($toggle, $menuEl, toggle) {
var self = this;
$menuEl.addClass('menu');
$toggle.on('click.menu', function(event) {
@@ -671,7 +676,7 @@ var OCP = {},
// close it
self.hideMenus();
}
- $menuEl.slideToggle(OC.menuSpeed);
+ $menuEl.slideToggle(OC.menuSpeed, toggle);
OC._currentMenu = $menuEl;
OC._currentMenuToggle = $toggle;
});
@@ -1398,6 +1403,7 @@ function initCore() {
// toggle the navigation
var $toggle = $('#header .header-appname-container');
var $navigation = $('#navigation');
+ var $appmenu = $('#appmenu');
// init the menu
OC.registerMenu($toggle, $navigation);
@@ -1427,6 +1433,20 @@ function initCore() {
OC.hideMenus(function(){return false});
}
});
+
+ $appmenu.delegate('a', 'click', function(event) {
+ var $app = $(event.target);
+ if(!$app.is('a')) {
+ $app = $app.closest('a');
+ }
+ if(event.which === 1 && !event.ctrlKey && !event.metaKey) {
+ $app.addClass('app-loading');
+ } else {
+ // Close navigation when opening app in
+ // a new tab
+ OC.hideMenus(function(){return false});
+ }
+ });
}
function setupUserMenu() {
@@ -1458,8 +1478,16 @@ function initCore() {
});
}
+ function setupContactsMenu() {
+ new OC.ContactsMenu({
+ el: $('#contactsmenu .menu'),
+ trigger: $('#contactsmenu .menutoggle')
+ });
+ }
+
setupMainMenu();
setupUserMenu();
+ setupContactsMenu();
// move triangle of apps dropdown to align with app name triangle
// 2 is the additional offset between the triangles
@@ -1482,6 +1510,52 @@ function initCore() {
});
}
+ var resizeMenu = function() {
+ var maxApps = 8;
+ var appList = $('#appmenu li');
+ var availableWidth = $('#header-left').width() - $('#nextcloud').width() - 44;
+ var appCount = Math.floor((availableWidth)/44);
+ // show a maximum of 8 apps
+ if(appCount >= maxApps) {
+ appCount = maxApps;
+ }
+ // show at least 2 apps in the popover
+ if(appList.length-1-appCount >= 1) {
+ appCount--;
+ }
+
+ $('#more-apps a').removeClass('active');
+ var lastShownApp;
+ for (var k = 0; k < appList.length-1; k++) {
+ var name = $(appList[k]).data('id');
+ if(k < appCount) {
+ $(appList[k]).removeClass('hidden');
+ $('#apps li[data-id=' + name + ']').addClass('in-header');
+ lastShownApp = appList[k];
+ } else {
+ $(appList[k]).addClass('hidden');
+ $('#apps li[data-id=' + name + ']').removeClass('in-header');
+ // move active app to last position if it is active
+ if(appCount > 0 && $(appList[k]).children('a').hasClass('active')) {
+ $(lastShownApp).addClass('hidden');
+ $('#apps li[data-id=' + $(lastShownApp).data('id') + ']').removeClass('in-header');
+ $(appList[k]).removeClass('hidden');
+ $('#apps li[data-id=' + name + ']').addClass('in-header');
+ }
+ }
+ }
+
+ // show/hide more apps icon
+ if($('#apps li:not(.in-header)').length === 0) {
+ $('#more-apps').hide();
+ $('#navigation').hide();
+ } else {
+ $('#more-apps').show();
+ }
+ };
+ $(window).resize(resizeMenu);
+ resizeMenu();
+
// just add snapper for logged in users
if($('#app-navigation').length && !$('html').hasClass('lte9')) {
diff --git a/core/js/login/authpicker.js b/core/js/login/authpicker.js
new file mode 100644
index 00000000000..6d8a6bb4160
--- /dev/null
+++ b/core/js/login/authpicker.js
@@ -0,0 +1,13 @@
+jQuery(document).ready(function() {
+ $('#app-token-login').click(function (e) {
+ e.preventDefault();
+ $(this).addClass('hidden');
+ $('#redirect-link').addClass('hidden');
+ $('#app-token-login-field').removeClass('hidden');
+ });
+
+ $('#submit-app-token-login').click(function(e) {
+ e.preventDefault();
+ window.location.href = 'nc://' + encodeURIComponent($('#user').val()) + ':' + encodeURIComponent($('#password').val()) + '@' + encodeURIComponent($('#serverHost').val());
+ });
+});
diff --git a/core/js/login/redirect.js b/core/js/login/redirect.js
new file mode 100644
index 00000000000..ea214feab2d
--- /dev/null
+++ b/core/js/login/redirect.js
@@ -0,0 +1,3 @@
+jQuery(document).ready(function() {
+ $('#submit-redirect-form').trigger('click');
+});
diff --git a/core/js/sharedialoglinkshareview.js b/core/js/sharedialoglinkshareview.js
index 6017714b305..9368982d916 100644
--- a/core/js/sharedialoglinkshareview.js
+++ b/core/js/sharedialoglinkshareview.js
@@ -60,14 +60,14 @@
'<input type="checkbox" name="showPassword" id="showPassword-{{cid}}" class="checkbox showPasswordCheckbox" {{#if isPasswordSet}}checked="checked"{{/if}} value="1" />' +
'<label for="showPassword-{{cid}}">{{enablePasswordLabel}}</label>' +
' {{/if}}' +
- '<div id="linkPass" class="linkPass {{#unless isPasswordSet}}hidden{{/unless}}">' +
+ '<div id="linkPass" class="oneline linkPass {{#unless isPasswordSet}}hidden{{/unless}}">' +
' <label for="linkPassText-{{cid}}" class="hidden-visually">{{passwordLabel}}</label>' +
' {{#if showPasswordCheckBox}}' +
' <input id="linkPassText-{{cid}}" class="linkPassText" type="password" placeholder="{{passwordPlaceholder}}" />' +
' {{else}}' +
' <input id="linkPassText-{{cid}}" class="linkPassText" type="password" placeholder="{{passwordPlaceholderInitial}}" />' +
' {{/if}}' +
- ' <span class="icon-loading-small hidden"></span>' +
+ ' <span class="icon icon-loading-small hidden"></span>' +
'</div>' +
'{{else}}' +
// FIXME: this doesn't belong in this view
@@ -307,10 +307,12 @@
this.model.saveLinkShare({
password: password
}, {
+ complete: function(model) {
+ $loading.removeClass('inlineblock').addClass('hidden');
+ },
error: function(model, msg) {
// destroy old tooltips
$input.tooltip('destroy');
- $loading.removeClass('inlineblock').addClass('hidden');
$input.addClass('error');
$input.attr('title', msg);
$input.tooltip({placement: 'bottom', trigger: 'manual'});
diff --git a/core/js/sharedialogview.js b/core/js/sharedialogview.js
index 16a2be0c4a9..3b09d13a7e2 100644
--- a/core/js/sharedialogview.js
+++ b/core/js/sharedialogview.js
@@ -22,7 +22,7 @@
'<div class="oneline">' +
' <input id="shareWith-{{cid}}" class="shareWithField" type="text" placeholder="{{sharePlaceholder}}" />' +
' <span class="shareWithLoading icon-loading-small hidden"></span>'+
- '{{{remoteShareInfo}}}' +
+ '{{{shareInfo}}}' +
'</div>' +
'{{/if}}' +
'<div class="shareeListView subView"></div>' +
@@ -30,9 +30,9 @@
'<div class="expirationView subView"></div>' +
'<div class="loading hidden" style="height: 50px"></div>';
- var TEMPLATE_REMOTE_SHARE_INFO =
- '<a target="_blank" class="icon icon-info shareWithRemoteInfo hasTooltip" href="{{docLink}}" ' +
- 'title="{{tooltip}}"></a>';
+ var TEMPLATE_SHARE_INFO =
+ '<span class="icon icon-info shareWithRemoteInfo hasTooltip" ' +
+ 'title="{{tooltip}}"></span>';
/**
* @class OCA.Share.ShareDialogView
@@ -135,7 +135,7 @@
var $shareWithField = $('.shareWithField'),
view = this,
$loading = this.$el.find('.shareWithLoading'),
- $remoteShareInfo = this.$el.find('.shareWithRemoteInfo');
+ $shareInfo = this.$el.find('.shareWithRemoteInfo');
var count = oc_config['sharing.minSearchStringLength'];
if (search.term.trim().length < count) {
@@ -160,7 +160,7 @@
$loading.removeClass('hidden');
$loading.addClass('inlineblock');
- $remoteShareInfo.addClass('hidden');
+ $shareInfo.addClass('hidden');
$shareWithField.removeClass('error')
.tooltip('hide');
@@ -177,7 +177,7 @@
function (result) {
$loading.addClass('hidden');
$loading.removeClass('inlineblock');
- $remoteShareInfo.removeClass('hidden');
+ $shareInfo.removeClass('hidden');
if (result.ocs.meta.statuscode === 100) {
var users = result.ocs.data.exact.users.concat(result.ocs.data.users);
var groups = result.ocs.data.exact.groups.concat(result.ocs.data.groups);
@@ -314,7 +314,7 @@
).fail(function() {
$loading.addClass('hidden');
$loading.removeClass('inlineblock');
- $remoteShareInfo.removeClass('hidden');
+ $shareInfo.removeClass('hidden');
OC.Notification.show(t('core', 'An error occurred. Please try again'));
window.setTimeout(OC.Notification.hide, 5000);
});
@@ -359,22 +359,22 @@
var $loading = this.$el.find('.shareWithLoading');
$loading.removeClass('hidden')
.addClass('inlineblock');
- var $remoteShareInfo = this.$el.find('.shareWithRemoteInfo');
- $remoteShareInfo.addClass('hidden');
+ var $shareInfo = this.$el.find('.shareWithRemoteInfo');
+ $shareInfo.addClass('hidden');
this.model.addShare(s.item.value, {success: function() {
$(e.target).val('')
.attr('disabled', false);
$loading.addClass('hidden')
.removeClass('inlineblock');
- $remoteShareInfo.removeClass('hidden');
+ $shareInfo.removeClass('hidden');
}, error: function(obj, msg) {
OC.Notification.showTemporary(msg);
$(e.target).attr('disabled', false)
.autocomplete('search', $(e.target).val());
$loading.addClass('hidden')
.removeClass('inlineblock');
- $remoteShareInfo.removeClass('hidden');
+ $shareInfo.removeClass('hidden');
}});
},
@@ -412,7 +412,7 @@
cid: this.cid,
shareLabel: t('core', 'Share'),
sharePlaceholder: this._renderSharePlaceholderPart(),
- remoteShareInfo: this._renderRemoteShareInfoPart(),
+ shareInfo: this._renderShareInfoPart(),
isSharingAllowed: this.model.sharePermissionPossible()
}));
@@ -457,47 +457,42 @@
this.linkShareView.showLink = this._showLink;
},
- _renderRemoteShareInfoPart: function() {
- var remoteShareInfo = '';
- if(this.configModel.get('isRemoteShareAllowed')) {
- var infoTemplate = this._getRemoteShareInfoTemplate();
- remoteShareInfo = infoTemplate({
- docLink: this.configModel.getFederatedShareDocLink(),
- tooltip: t('core', 'Share with people on other servers using their Federated Cloud ID username@example.com/nextcloud')
+ _renderShareInfoPart: function() {
+ var shareInfo = '';
+ var infoTemplate = this._getShareInfoTemplate();
+
+ if(this.configModel.get('isMailShareAllowed') && this.configModel.get('isRemoteShareAllowed')) {
+ shareInfo = infoTemplate({
+ tooltip: t('core', 'Share with other people by entering a user or group, a federated cloud ID or an email address.')
+ });
+ } else if(this.configModel.get('isRemoteShareAllowed')) {
+ shareInfo = infoTemplate({
+ tooltip: t('core', 'Share with other people by entering a user or group or a federated cloud ID.')
+ });
+ } else if(this.configModel.get('isMailShareAllowed')) {
+ shareInfo = infoTemplate({
+ tooltip: t('core', 'Share with other people by entering a user or group or an email address.')
});
}
- return remoteShareInfo;
+ return shareInfo;
},
_renderSharePlaceholderPart: function () {
- var allowGroupSharing = this.configModel.get('allowGroupSharing');
var allowRemoteSharing = this.configModel.get('isRemoteShareAllowed');
var allowMailSharing = this.configModel.get('isMailShareAllowed');
- if (!allowGroupSharing && !allowRemoteSharing && allowMailSharing) {
- return t('core', 'Share with users or by mail...');
- }
- if (!allowGroupSharing && allowRemoteSharing && !allowMailSharing) {
- return t('core', 'Share with users or remote users...');
- }
- if (!allowGroupSharing && allowRemoteSharing && allowMailSharing) {
- return t('core', 'Share with users, remote users or by mail...');
- }
- if (allowGroupSharing && !allowRemoteSharing && !allowMailSharing) {
- return t('core', 'Share with users or groups...');
- }
- if (allowGroupSharing && !allowRemoteSharing && allowMailSharing) {
- return t('core', 'Share with users, groups or by mail...');
+ if (!allowRemoteSharing && allowMailSharing) {
+ return t('core', 'Name or email address...');
}
- if (allowGroupSharing && allowRemoteSharing && !allowMailSharing) {
- return t('core', 'Share with users, groups or remote users...');
+ if (allowRemoteSharing && !allowMailSharing) {
+ return t('core', 'Name or federated cloud ID...');
}
- if (allowGroupSharing && allowRemoteSharing && allowMailSharing) {
- return t('core', 'Share with users, groups, remote users or by mail...');
+ if (allowRemoteSharing && allowMailSharing) {
+ return t('core', 'Name, federated cloud ID or email address...');
}
- return t('core', 'Share with users...');
+ return t('core', 'Name...');
},
/**
@@ -520,8 +515,8 @@
* @returns {Function}
* @private
*/
- _getRemoteShareInfoTemplate: function() {
- return this._getTemplate('remoteShareInfo', TEMPLATE_REMOTE_SHARE_INFO);
+ _getShareInfoTemplate: function() {
+ return this._getTemplate('shareInfo', TEMPLATE_SHARE_INFO);
}
});
diff --git a/core/js/shareitemmodel.js b/core/js/shareitemmodel.js
index bff006f7ef3..41f9eb5e0aa 100644
--- a/core/js/shareitemmodel.js
+++ b/core/js/shareitemmodel.js
@@ -104,7 +104,14 @@
/**
* Saves the current link share information.
*
- * This will trigger an ajax call and refetch the model afterwards.
+ * This will trigger an ajax call and, if successful, refetch the model
+ * afterwards. Callbacks "success", "error" and "complete" can be given
+ * in the options object; "success" is called after a successful save
+ * once the model is refetch, "error" is called after a failed save, and
+ * "complete" is called both after a successful save and after a failed
+ * save. Note that "complete" is called before "success" and "error" are
+ * called (unlike in jQuery, in which it is called after them); this
+ * ensures that "complete" is called even if refetching the model fails.
*
* TODO: this should be a separate model
*/
@@ -149,7 +156,6 @@
addShare: function(attributes, options) {
var shareType = attributes.shareType;
- options = options || {};
attributes = _.extend({}, attributes);
// Default permissions are Edit (CRUD) and Share
@@ -173,53 +179,43 @@
attributes.path = this.fileInfoModel.getFullPath();
}
- var self = this;
- return $.ajax({
+ return this._addOrUpdateShare({
type: 'POST',
url: this._getUrl('shares'),
data: attributes,
dataType: 'json'
- }).done(function() {
- self.fetch().done(function() {
- if (_.isFunction(options.success)) {
- options.success(self);
- }
- });
- }).fail(function(xhr) {
- var msg = t('core', 'Error');
- var result = xhr.responseJSON;
- if (result && result.ocs && result.ocs.meta) {
- msg = result.ocs.meta.message;
- }
-
- if (_.isFunction(options.error)) {
- options.error(self, msg);
- } else {
- OC.dialogs.alert(msg, t('core', 'Error while sharing'));
- }
- });
+ }, options);
},
updateShare: function(shareId, attrs, options) {
- var self = this;
- options = options || {};
- return $.ajax({
+ return this._addOrUpdateShare({
type: 'PUT',
url: this._getUrl('shares/' + encodeURIComponent(shareId)),
data: attrs,
dataType: 'json'
+ }, options);
+ },
+
+ _addOrUpdateShare: function(ajaxSettings, options) {
+ var self = this;
+ options = options || {};
+
+ return $.ajax(
+ ajaxSettings
+ ).always(function() {
+ if (_.isFunction(options.complete)) {
+ options.complete(self);
+ }
}).done(function() {
- self.fetch({
- success: function() {
- if (_.isFunction(options.success)) {
- options.success(self);
- }
+ self.fetch().done(function() {
+ if (_.isFunction(options.success)) {
+ options.success(self);
}
});
}).fail(function(xhr) {
var msg = t('core', 'Error');
var result = xhr.responseJSON;
- if (result.ocs && result.ocs.meta) {
+ if (result && result.ocs && result.ocs.meta) {
msg = result.ocs.meta.message;
}
@@ -802,7 +798,7 @@
isLinkShare: true,
id: share.id,
token: share.token,
- password: share.password,
+ password: share.share_with,
link: link,
permissions: share.permissions,
// currently expiration is only effective for link shares.
diff --git a/core/js/tests/specs/contactsmenuSpec.js b/core/js/tests/specs/contactsmenuSpec.js
new file mode 100644
index 00000000000..8e57dc35f01
--- /dev/null
+++ b/core/js/tests/specs/contactsmenuSpec.js
@@ -0,0 +1,265 @@
+/* global expect, sinon, _, spyOn, Promise */
+
+/**
+ * @copyright 2017 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @author 2017 Christoph Wurst <christoph@winzerhof-wurst.at>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+describe('Contacts menu', function() {
+ var $triggerEl,
+ $menuEl,
+ menu;
+
+ /**
+ * @private
+ * @returns {Promise}
+ */
+ function openMenu() {
+ return menu._toggleVisibility(true);
+ }
+
+ beforeEach(function(done) {
+ $triggerEl = $('<div class="menutoggle">');
+ $menuEl = $('<div class="menu">');
+
+ menu = new OC.ContactsMenu({
+ el: $menuEl,
+ trigger: $triggerEl
+ });
+ done();
+ });
+
+ it('shows a loading message while data is being fetched', function() {
+ fakeServer.respondWith('GET', OC.generateUrl('/contactsmenu/contacts'), [
+ 200,
+ {},
+ ''
+ ]);
+
+ openMenu();
+
+ expect($menuEl.html()).toContain('Loading your contacts …');
+ });
+
+ it('shows an error message when loading the contacts data fails', function(done) {
+ spyOn(console, 'error');
+ fakeServer.respondWith('GET', OC.generateUrl('/contactsmenu/contacts'), [
+ 500,
+ {},
+ ''
+ ]);
+
+ var opening = openMenu();
+
+ expect($menuEl.html()).toContain('Loading your contacts …');
+ fakeServer.respond();
+
+ opening.then(function() {
+ expect($menuEl.html()).toContain('There was an error loading your contacts');
+ expect(console.error).toHaveBeenCalledTimes(1);
+ done();
+ }, function(e) {
+ done.fail(e);
+ });
+ });
+
+ it('loads data successfully', function(done) {
+ spyOn(menu, '_getContacts').and.returnValue(Promise.resolve({
+ contacts: [
+ {
+ id: null,
+ fullName: 'Acosta Lancaster',
+ topAction: {
+ title: 'Mail',
+ icon: 'icon-mail',
+ hyperlink: 'mailto:deboraoliver%40centrexin.com'
+ },
+ actions: [
+ {
+ title: 'Mail',
+ icon: 'icon-mail',
+ hyperlink: 'mailto:mathisholland%40virxo.com'
+ },
+ {
+ title: 'Details',
+ icon: 'icon-info',
+ hyperlink: 'https:\/\/localhost\/index.php\/apps\/contacts'
+ }
+ ],
+ lastMessage: ''
+ },
+ {
+ id: null,
+ fullName: 'Adeline Snider',
+ topAction: {
+ title: 'Mail',
+ icon: 'icon-mail',
+ hyperlink: 'mailto:ceciliasoto%40essensia.com'
+ },
+ actions: [
+ {
+ title: 'Mail',
+ icon: 'icon-mail',
+ hyperlink: 'mailto:pearliesellers%40inventure.com'
+ },
+ {
+ title: 'Details',
+ icon: 'icon-info',
+ hyperlink: 'https://localhost\/index.php\/apps\/contacts'
+ }
+ ],
+ lastMessage: 'cu'
+ }
+ ],
+ contactsAppEnabled: true
+ }));
+
+ openMenu().then(function() {
+ expect(menu._getContacts).toHaveBeenCalled();
+ expect($menuEl.html()).toContain('Acosta Lancaster');
+ expect($menuEl.html()).toContain('Adeline Snider');
+ expect($menuEl.html()).toContain('Show all contacts …');
+ done();
+ }, function(e) {
+ done.fail(e);
+ });
+
+ });
+
+ it('doesn\'t show a link to the contacts app if it\'s disabled', function(done) {
+ spyOn(menu, '_getContacts').and.returnValue(Promise.resolve({
+ contacts: [
+ {
+ id: null,
+ fullName: 'Acosta Lancaster',
+ topAction: {
+ title: 'Mail',
+ icon: 'icon-mail',
+ hyperlink: 'mailto:deboraoliver%40centrexin.com'
+ },
+ actions: [
+ {
+ title: 'Mail',
+ icon: 'icon-mail',
+ hyperlink: 'mailto:mathisholland%40virxo.com'
+ },
+ {
+ title: 'Details',
+ icon: 'icon-info',
+ hyperlink: 'https:\/\/localhost\/index.php\/apps\/contacts'
+ }
+ ],
+ lastMessage: ''
+ }
+ ],
+ contactsAppEnabled: false
+ }));
+
+ openMenu().then(function() {
+ expect(menu._getContacts).toHaveBeenCalled();
+ expect($menuEl.html()).not.toContain('Show all contacts …');
+ done();
+ }, function(e) {
+ done.fail(e);
+ });
+ });
+
+ it('shows only one entry\'s action menu at a time', function(done) {
+ spyOn(menu, '_getContacts').and.returnValue(Promise.resolve({
+ contacts: [
+ {
+ id: null,
+ fullName: 'Acosta Lancaster',
+ topAction: {
+ title: 'Mail',
+ icon: 'icon-mail',
+ hyperlink: 'mailto:deboraoliver%40centrexin.com'
+ },
+ actions: [
+ {
+ title: 'Info',
+ icon: 'icon-info',
+ hyperlink: 'https:\/\/localhost\/index.php\/apps\/contacts'
+ },
+ {
+ title: 'Details',
+ icon: 'icon-info',
+ hyperlink: 'https:\/\/localhost\/index.php\/apps\/contacts'
+ }
+ ],
+ lastMessage: ''
+ },
+ {
+ id: null,
+ fullName: 'Adeline Snider',
+ topAction: {
+ title: 'Mail',
+ icon: 'icon-mail',
+ hyperlink: 'mailto:ceciliasoto%40essensia.com'
+ },
+ actions: [
+ {
+ title: 'Info',
+ icon: 'icon-info',
+ hyperlink: 'https://localhost\/index.php\/apps\/contacts'
+ },
+ {
+ title: 'Details',
+ icon: 'icon-info',
+ hyperlink: 'https://localhost\/index.php\/apps\/contacts'
+ }
+ ],
+ lastMessage: 'cu'
+ }
+ ],
+ contactsAppEnabled: true
+ }));
+
+ openMenu().then(function() {
+ expect(menu._getContacts).toHaveBeenCalled();
+ expect($menuEl.html()).toContain('Adeline Snider');
+ expect($menuEl.html()).toContain('Show all contacts …');
+
+ // Both menus are closed at the beginning
+ expect($menuEl.find('.contact').eq(0).find('.menu').is(':visible')).toBe(false);
+ expect($menuEl.find('.contact').eq(1).find('.menu').is(':visible')).toBe(false);
+
+ // Open the first one
+ $menuEl.find('.contact').eq(0).find('.other-actions').click();
+ expect($menuEl.find('.contact').eq(0).find('.menu').css('display')).toBe('block');
+ expect($menuEl.find('.contact').eq(1).find('.menu').css('display')).toBe('none');
+
+ // Open the second one
+ $menuEl.find('.contact').eq(1).find('.other-actions').click();
+ expect($menuEl.find('.contact').eq(0).find('.menu').css('display')).toBe('none');
+ expect($menuEl.find('.contact').eq(1).find('.menu').css('display')).toBe('block');
+
+ // Close the second one
+ $menuEl.find('.contact').eq(1).find('.other-actions').click();
+ expect($menuEl.find('.contact').eq(0).find('.menu').css('display')).toBe('none');
+ expect($menuEl.find('.contact').eq(1).find('.menu').css('display')).toBe('none');
+
+ done();
+ }, function(e) {
+ done.fail(e);
+ });
+ });
+
+});
diff --git a/core/js/tests/specs/jquery.avatarSpec.js b/core/js/tests/specs/jquery.avatarSpec.js
index 9bb10c41be7..dab78500d0b 100644
--- a/core/js/tests/specs/jquery.avatarSpec.js
+++ b/core/js/tests/specs/jquery.avatarSpec.js
@@ -186,7 +186,7 @@ describe('jquery.avatar tests', function() {
});
it('with ie8 fix', function() {
- sinon.stub(Math, 'random', function() {
+ sinon.stub(Math, 'random').callsFake(function() {
return 0.5;
});
diff --git a/core/js/tests/specs/sharedialoglinkshareview.js b/core/js/tests/specs/sharedialoglinkshareview.js
new file mode 100644
index 00000000000..811919b5603
--- /dev/null
+++ b/core/js/tests/specs/sharedialoglinkshareview.js
@@ -0,0 +1,143 @@
+/**
+ *
+ * @copyright Copyright (c) 2015, Tom Needham (tom@owncloud.com)
+ * @copyright Copyright (c) 2017, Daniel Calviño Sánchez (danxuliu@gmail.com)
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+describe('OC.Share.ShareDialogLinkShareView', function () {
+
+ var configModel;
+ var shareModel;
+ var view;
+
+ beforeEach(function () {
+
+ var fileInfoModel = new OCA.Files.FileInfoModel({
+ id: 123,
+ name: 'shared_file_name.txt',
+ path: '/subdir',
+ size: 100,
+ mimetype: 'text/plain',
+ permissions: OC.PERMISSION_ALL,
+ sharePermissions: OC.PERMISSION_ALL
+ });
+
+ var attributes = {
+ itemType: fileInfoModel.isDirectory() ? 'folder' : 'file',
+ itemSource: fileInfoModel.get('id'),
+ possiblePermissions: OC.PERMISSION_ALL,
+ permissions: OC.PERMISSION_ALL
+ };
+
+ configModel = new OC.Share.ShareConfigModel({
+ enforcePasswordForPublicLink: false,
+ isResharingAllowed: true,
+ enforcePasswordForPublicLink: false,
+ isDefaultExpireDateEnabled: false,
+ isDefaultExpireDateEnforced: false,
+ defaultExpireDate: 7
+ });
+
+ sinon.stub(configModel, 'isShareWithLinkAllowed');
+
+ shareModel = new OC.Share.ShareItemModel(attributes, {
+ configModel: configModel,
+ fileInfoModel: fileInfoModel
+ });
+
+ view = new OC.Share.ShareDialogLinkShareView({
+ configModel: configModel,
+ model: shareModel
+ });
+
+ });
+
+ afterEach(function () {
+ view.remove();
+ configModel.isShareWithLinkAllowed.restore();
+ });
+
+ describe('onPasswordEntered', function () {
+
+ var $passwordText;
+ var $workingIcon;
+
+ beforeEach(function () {
+
+ // Needed to render the view
+ configModel.isShareWithLinkAllowed.returns(true);
+
+ // Setting the share also triggers the rendering
+ shareModel.set({
+ linkShare: {
+ isLinkShare: true,
+ password: 'password'
+ }
+ });
+
+ var $passwordDiv = view.$el.find('#linkPass');
+ $passwordText = view.$el.find('.linkPassText');
+ $workingIcon = view.$el.find('.linkPass .icon-loading-small');
+
+ sinon.stub(shareModel, 'saveLinkShare');
+
+ expect($passwordDiv.hasClass('hidden')).toBeFalsy();
+ expect($passwordText.hasClass('hidden')).toBeFalsy();
+ expect($workingIcon.hasClass('hidden')).toBeTruthy();
+
+ $passwordText.val('myPassword');
+ });
+
+ afterEach(function () {
+ shareModel.saveLinkShare.restore();
+ });
+
+ it('shows the working icon when called', function () {
+ view.onPasswordEntered();
+
+ expect($workingIcon.hasClass('hidden')).toBeFalsy();
+ expect(shareModel.saveLinkShare.withArgs({ password: 'myPassword' }).calledOnce).toBeTruthy();
+ });
+
+ it('hides the working icon when saving the password succeeds', function () {
+ view.onPasswordEntered();
+
+ expect($workingIcon.hasClass('hidden')).toBeFalsy();
+ expect(shareModel.saveLinkShare.withArgs({ password: 'myPassword' }).calledOnce).toBeTruthy();
+
+ shareModel.saveLinkShare.yieldTo("complete", [shareModel]);
+
+ expect($workingIcon.hasClass('hidden')).toBeTruthy();
+ });
+
+ it('hides the working icon when saving the password fails', function () {
+ view.onPasswordEntered();
+
+ expect($workingIcon.hasClass('hidden')).toBeFalsy();
+ expect(shareModel.saveLinkShare.withArgs({ password: 'myPassword' }).calledOnce).toBeTruthy();
+
+ shareModel.saveLinkShare.yieldTo("complete", [shareModel]);
+ shareModel.saveLinkShare.yieldTo("error", [shareModel, "The error message"]);
+
+ expect($workingIcon.hasClass('hidden')).toBeTruthy();
+ });
+
+ });
+
+});
diff --git a/core/js/tests/specs/sharedialogviewSpec.js b/core/js/tests/specs/sharedialogviewSpec.js
index 307adea85ff..95349bc4875 100644
--- a/core/js/tests/specs/sharedialogviewSpec.js
+++ b/core/js/tests/specs/sharedialogviewSpec.js
@@ -19,7 +19,7 @@
*
*/
-/* global oc_appconfig */
+/* global oc_appconfig, sinon */
describe('OC.Share.ShareDialogView', function() {
var $container;
var oldAppConfig;
@@ -90,7 +90,7 @@ describe('OC.Share.ShareDialogView', function() {
linkShare: {isLinkShare: false}
});
- autocompleteStub = sinon.stub($.fn, 'autocomplete', function() {
+ autocompleteStub = sinon.stub($.fn, 'autocomplete').callsFake(function() {
// dummy container with the expected attributes
if (!$(this).length) {
// simulate the real autocomplete that returns
diff --git a/core/js/tests/specs/shareitemmodelSpec.js b/core/js/tests/specs/shareitemmodelSpec.js
index 3d3baf75d15..771a9263709 100644
--- a/core/js/tests/specs/shareitemmodelSpec.js
+++ b/core/js/tests/specs/shareitemmodelSpec.js
@@ -670,6 +670,83 @@ describe('OC.Share.ShareItemModel', function() {
shareWith: 'group1'
});
});
+ it('calls complete handler before refreshing the model', function() {
+ var completeStub = sinon.stub();
+ model.addShare({
+ shareType: OC.Share.SHARE_TYPE_GROUP,
+ shareWith: 'group1'
+ }, {
+ complete: completeStub
+ });
+
+ expect(fakeServer.requests.length).toEqual(1);
+ fakeServer.requests[0].respond(
+ 200,
+ { 'Content-Type': 'application/json' },
+ JSON.stringify({ })
+ );
+
+ expect(completeStub.calledOnce).toEqual(true);
+ expect(completeStub.lastCall.args[0]).toEqual(model);
+
+ fetchReshareDeferred.resolve(makeOcsResponse([]));
+ fetchSharesDeferred.resolve(makeOcsResponse([]));
+
+ expect(completeStub.calledOnce).toEqual(true);
+ });
+ it('calls success handler after refreshing the model', function() {
+ var successStub = sinon.stub();
+ model.addShare({
+ shareType: OC.Share.SHARE_TYPE_GROUP,
+ shareWith: 'group1'
+ }, {
+ success: successStub
+ });
+
+ expect(fakeServer.requests.length).toEqual(1);
+ fakeServer.requests[0].respond(
+ 200,
+ { 'Content-Type': 'application/json' },
+ JSON.stringify({ })
+ );
+
+ expect(successStub.called).toEqual(false);
+
+ fetchReshareDeferred.resolve(makeOcsResponse([]));
+ fetchSharesDeferred.resolve(makeOcsResponse([]));
+
+ expect(successStub.calledOnce).toEqual(true);
+ expect(successStub.lastCall.args[0]).toEqual(model);
+ });
+ it('calls complete handler before error handler', function() {
+ var completeStub = sinon.stub();
+ var errorStub = sinon.stub();
+ model.addShare({
+ shareType: OC.Share.SHARE_TYPE_GROUP,
+ shareWith: 'group1'
+ }, {
+ complete: completeStub,
+ error: errorStub
+ });
+
+ expect(fakeServer.requests.length).toEqual(1);
+ fakeServer.requests[0].respond(
+ 400,
+ { 'Content-Type': 'application/json' },
+ JSON.stringify({
+ ocs: {
+ meta: {
+ message: 'Some error message'
+ }
+ }
+ })
+ );
+
+ expect(completeStub.calledOnce).toEqual(true);
+ expect(completeStub.lastCall.args[0]).toEqual(model);
+ expect(errorStub.calledOnce).toEqual(true);
+ expect(completeStub.calledBefore(errorStub)).toEqual(true);
+ });
it('calls error handler with error message', function() {
var errorStub = sinon.stub();
model.addShare({
@@ -693,6 +770,7 @@ describe('OC.Share.ShareItemModel', function() {
);
expect(errorStub.calledOnce).toEqual(true);
+ expect(errorStub.lastCall.args[0]).toEqual(model);
expect(errorStub.lastCall.args[1]).toEqual('Some error message');
});
});
@@ -712,6 +790,80 @@ describe('OC.Share.ShareItemModel', function() {
permissions: '' + (OC.PERMISSION_READ | OC.PERMISSION_SHARE)
});
});
+ it('calls complete handler before refreshing the model', function() {
+ var completeStub = sinon.stub();
+ model.updateShare(123, {
+ permissions: OC.PERMISSION_READ | OC.PERMISSION_SHARE
+ }, {
+ complete: completeStub
+ });
+
+ expect(fakeServer.requests.length).toEqual(1);
+ fakeServer.requests[0].respond(
+ 200,
+ { 'Content-Type': 'application/json' },
+ JSON.stringify({ })
+ );
+
+ expect(completeStub.calledOnce).toEqual(true);
+ expect(completeStub.lastCall.args[0]).toEqual(model);
+
+ fetchReshareDeferred.resolve(makeOcsResponse([]));
+ fetchSharesDeferred.resolve(makeOcsResponse([]));
+
+ expect(completeStub.calledOnce).toEqual(true);
+ });
+ it('calls success handler after refreshing the model', function() {
+ var successStub = sinon.stub();
+ model.updateShare(123, {
+ permissions: OC.PERMISSION_READ | OC.PERMISSION_SHARE
+ }, {
+ success: successStub
+ });
+
+ expect(fakeServer.requests.length).toEqual(1);
+ fakeServer.requests[0].respond(
+ 200,
+ { 'Content-Type': 'application/json' },
+ JSON.stringify({ })
+ );
+
+ expect(successStub.called).toEqual(false);
+
+ fetchReshareDeferred.resolve(makeOcsResponse([]));
+ fetchSharesDeferred.resolve(makeOcsResponse([]));
+
+ expect(successStub.calledOnce).toEqual(true);
+ expect(successStub.lastCall.args[0]).toEqual(model);
+ });
+ it('calls complete handler before error handler', function() {
+ var completeStub = sinon.stub();
+ var errorStub = sinon.stub();
+ model.updateShare(123, {
+ permissions: OC.PERMISSION_READ | OC.PERMISSION_SHARE
+ }, {
+ complete: completeStub,
+ error: errorStub
+ });
+
+ expect(fakeServer.requests.length).toEqual(1);
+ fakeServer.requests[0].respond(
+ 400,
+ { 'Content-Type': 'application/json' },
+ JSON.stringify({
+ ocs: {
+ meta: {
+ message: 'Some error message'
+ }
+ }
+ })
+ );
+
+ expect(completeStub.calledOnce).toEqual(true);
+ expect(completeStub.lastCall.args[0]).toEqual(model);
+ expect(errorStub.calledOnce).toEqual(true);
+ expect(completeStub.calledBefore(errorStub)).toEqual(true);
+ });
it('calls error handler with error message', function() {
var errorStub = sinon.stub();
model.updateShare(123, {
@@ -734,6 +886,7 @@ describe('OC.Share.ShareItemModel', function() {
);
expect(errorStub.calledOnce).toEqual(true);
+ expect(errorStub.lastCall.args[0]).toEqual(model);
expect(errorStub.lastCall.args[1]).toEqual('Some error message');
});
});