[Pkg-owncloud-commits] [owncloud] 163/199: Sharing overview fixes and unit tests

David Prévot taffit at moszumanska.debian.org
Sun Jun 1 18:53:22 UTC 2014


This is an automated email from the git hooks/post-receive script.

taffit pushed a commit to branch master
in repository owncloud.

commit 1d9129eac35b49a4e8d0d642a68d7d634f31c905
Author: Vincent Petry <pvince81 at owncloud.com>
Date:   Wed May 21 12:54:34 2014 +0200

    Sharing overview fixes and unit tests
    
    - Fixed renaming and fileActionsReady event
    - Added unit tests for shares list
    - Fixed public page with defer
    - Fixed file actions in sharing overview
    - Fixed sharing counterpart list (10 entries max)
    - Fixed file path attribute to be used in download action
    - Fix sharing list headers
    - OC.Share icons now operate on fileList instance
    - Fix OC.Share.updateIcon when more than one list in DOM
---
 apps/files/js/app.js                              |   8 +
 apps/files/js/fileactions.js                      |  15 +-
 apps/files/js/filelist.js                         |  19 +-
 apps/files/tests/js/fileactionsSpec.js            |  29 +-
 apps/files/tests/js/filelistSpec.js               |   2 +-
 apps/files_sharing/js/app.js                      |  29 +-
 apps/files_sharing/js/public.js                   |   5 +-
 apps/files_sharing/js/share.js                    |   9 +-
 apps/files_sharing/js/sharedfilelist.js           |  56 ++-
 apps/files_sharing/templates/list.php             |   4 +-
 apps/files_sharing/tests/js/appSpec.js            | 140 ++++++++
 apps/files_sharing/tests/js/sharedfilelistSpec.js | 417 ++++++++++++++++++++++
 core/js/share.js                                  |  45 +--
 tests/karma.config.js                             |  43 ++-
 14 files changed, 751 insertions(+), 70 deletions(-)

diff --git a/apps/files/js/app.js b/apps/files/js/app.js
index 6ccf513..7180294 100644
--- a/apps/files/js/app.js
+++ b/apps/files/js/app.js
@@ -73,6 +73,14 @@
 		},
 
 		/**
+		 * Returns the view id of the currently active view
+		 * @return view id
+		 */
+		getActiveView: function() {
+			return this.navigation.getActiveItem();
+		},
+
+		/**
 		 * Setup events based on URL changes
 		 */
 		_setupEvents: function() {
diff --git a/apps/files/js/fileactions.js b/apps/files/js/fileactions.js
index 8cee037..3df62f3 100644
--- a/apps/files/js/fileactions.js
+++ b/apps/files/js/fileactions.js
@@ -232,7 +232,7 @@
 			}
 
 			if (triggerEvent){
-				fileList.$fileList.trigger(jQuery.Event("fileActionsReady"));
+				fileList.$fileList.trigger(jQuery.Event("fileActionsReady", {fileList: fileList}));
 			}
 		},
 		getCurrentFile: function () {
@@ -252,8 +252,6 @@
 		 * Register the actions that are used by default for the files app.
 		 */
 		registerDefaultActions: function() {
-			// TODO: try to find a way to not make it depend on fileList,
-			// maybe get a handler or listener to trigger events on
 			this.register('all', 'Delete', OC.PERMISSION_DELETE, function () {
 				return OC.imagePath('core', 'actions/delete');
 			}, function (filename, context) {
@@ -287,7 +285,8 @@
 			this.register(downloadScope, 'Download', OC.PERMISSION_READ, function () {
 				return OC.imagePath('core', 'actions/download');
 			}, function (filename, context) {
-				var url = context.fileList.getDownloadUrl(filename, context.fileList.getCurrentDirectory());
+				var dir = context.dir || context.fileList.getCurrentDirectory();
+				var url = context.fileList.getDownloadUrl(filename, dir);
 				if (url) {
 					OC.redirect(url);
 				}
@@ -309,11 +308,13 @@
 	// through window.FileActions will be limited to the main file list.
 	window.FileActions = OCA.Files.legacyFileActions;
 	window.FileActions.register = function (mime, name, permissions, icon, action, displayName) {
-		console.warn('FileActions.register() is deprecated, please use OCA.Files.fileActions.register() instead');
-		OCA.Files.FileActions.prototype.register.call(window.FileActions, mime, name, permissions, icon, action, displayName);
+		console.warn('FileActions.register() is deprecated, please use OCA.Files.fileActions.register() instead', arguments);
+		OCA.Files.FileActions.prototype.register.call(
+				window.FileActions, mime, name, permissions, icon, action, displayName
+		);
 	};
 	window.FileActions.setDefault = function (mime, name) {
-		console.warn('FileActions.setDefault() is deprecated, please use OCA.Files.fileActions.setDefault() instead');
+		console.warn('FileActions.setDefault() is deprecated, please use OCA.Files.fileActions.setDefault() instead', mime, name);
 		OCA.Files.FileActions.prototype.setDefault.call(window.FileActions, mime, name);
 	};
 })();
diff --git a/apps/files/js/filelist.js b/apps/files/js/filelist.js
index 52d4c8b..68b2220 100644
--- a/apps/files/js/filelist.js
+++ b/apps/files/js/filelist.js
@@ -508,7 +508,7 @@
 			this.$el.find('thead').after(this.$fileList);
 
 			this.updateEmptyContent();
-			this.$fileList.trigger(jQuery.Event("fileActionsReady"));
+			this.$fileList.trigger($.Event('fileActionsReady', {fileList: this}));
 
 			this.fileSummary.calculate(filesArray);
 
@@ -530,7 +530,7 @@
 				type = fileData.type || 'file',
 				mtime = parseInt(fileData.mtime, 10) || new Date().getTime(),
 				mime = fileData.mimetype,
-				path = fileData.path || this.getCurrentDirectory(),
+				path = fileData.path,
 				linkUrl;
 			options = options || {};
 
@@ -550,6 +550,13 @@
 				"data-permissions": fileData.permissions || this.getDirectoryPermissions()
 			});
 
+			if (!_.isUndefined(path)) {
+				tr.attr('data-path', path);
+			}
+			else {
+				path = this.getCurrentDirectory();
+			}
+
 			if (type === 'dir') {
 				// use default folder icon
 				icon = icon || OC.imagePath('core', 'filetypes/folder');
@@ -1224,16 +1231,16 @@
 								// reinsert row
 								self.files.splice(tr.index(), 1);
 								tr.remove();
-								self.add(fileInfo, {updateSummary: false});
-								self.$fileList.trigger($.Event('fileActionsReady'));
+								self.add(fileInfo, {updateSummary: false, silent: true});
+								self.$fileList.trigger($.Event('fileActionsReady', {fileList: self}));
 							}
 						});
 					} else {
 						// add back the old file info when cancelled
 						self.files.splice(tr.index(), 1);
 						tr.remove();
-						self.add(oldFileInfo, {updateSummary: false});
-						self.$fileList.trigger($.Event('fileActionsReady'));
+						self.add(oldFileInfo, {updateSummary: false, silent: true});
+						self.$fileList.trigger($.Event('fileActionsReady', {fileList: self}));
 					}
 				} catch (error) {
 					input.attr('title', error);
diff --git a/apps/files/tests/js/fileactionsSpec.js b/apps/files/tests/js/fileactionsSpec.js
index fa634da..490594a 100644
--- a/apps/files/tests/js/fileactionsSpec.js
+++ b/apps/files/tests/js/fileactionsSpec.js
@@ -104,7 +104,34 @@ describe('OCA.Files.FileActions tests', function() {
 		$tr.find('.action-download').click();
 
 		expect(redirectStub.calledOnce).toEqual(true);
-		expect(redirectStub.getCall(0).args[0]).toEqual(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2Fsubdir&files=testName.txt');
+		expect(redirectStub.getCall(0).args[0]).toEqual(
+			OC.webroot +
+			'/index.php/apps/files/ajax/download.php' +
+			'?dir=%2Fsubdir&files=testName.txt');
+		redirectStub.restore();
+	});
+	it('takes the file\'s path into account when clicking download', function() {
+		var redirectStub = sinon.stub(OC, 'redirect');
+		var fileData = {
+			id: 18,
+			type: 'file',
+			name: 'testName.txt',
+			path: '/anotherpath/there',
+			mimetype: 'text/plain',
+			size: '1234',
+			etag: 'a01234c',
+			mtime: '123456'
+		};
+		var $tr = fileList.add(fileData);
+		FileActions.display($tr.find('td.filename'), true, fileList);
+
+		$tr.find('.action-download').click();
+
+		expect(redirectStub.calledOnce).toEqual(true);
+		expect(redirectStub.getCall(0).args[0]).toEqual(
+			OC.webroot + '/index.php/apps/files/ajax/download.php' +
+			'?dir=%2Fanotherpath%2Fthere&files=testName.txt'
+		);
 		redirectStub.restore();
 	});
 	it('deletes file when clicking delete', function() {
diff --git a/apps/files/tests/js/filelistSpec.js b/apps/files/tests/js/filelistSpec.js
index a197fd5..3e9950d 100644
--- a/apps/files/tests/js/filelistSpec.js
+++ b/apps/files/tests/js/filelistSpec.js
@@ -483,7 +483,7 @@ describe('OCA.Files.FileList tests', function() {
 			var $input, request;
 
 			for (var i = 0; i < testFiles.length; i++) {
-				fileList.add(testFiles[i]);
+				fileList.add(testFiles[i], {silent: true});
 			}
 
 			// trigger rename prompt
diff --git a/apps/files_sharing/js/app.js b/apps/files_sharing/js/app.js
index 7a71684..3764328 100644
--- a/apps/files_sharing/js/app.js
+++ b/apps/files_sharing/js/app.js
@@ -16,7 +16,7 @@ OCA.Sharing.App = {
 
 	initSharingIn: function($el) {
 		if (this._inFileList) {
-			return;
+			return this._inFileList;
 		}
 
 		this._inFileList = new OCA.Sharing.FileList(
@@ -31,11 +31,12 @@ OCA.Sharing.App = {
 		this._extendFileList(this._inFileList);
 		this._inFileList.appName = t('files_sharing', 'Shared with you');
 		this._inFileList.$el.find('#emptycontent').text(t('files_sharing', 'No files have been shared with you yet.'));
+		return this._inFileList;
 	},
 
 	initSharingOut: function($el) {
 		if (this._outFileList) {
-			return;
+			return this._outFileList;
 		}
 		this._outFileList = new OCA.Sharing.FileList(
 			$el,
@@ -49,6 +50,19 @@ OCA.Sharing.App = {
 		this._extendFileList(this._outFileList);
 		this._outFileList.appName = t('files_sharing', 'Shared with others');
 		this._outFileList.$el.find('#emptycontent').text(t('files_sharing', 'You haven\'t shared any files yet.'));
+		return this._outFileList;
+	},
+
+	removeSharingIn: function() {
+		if (this._inFileList) {
+			this._inFileList.$fileList.empty();
+		}
+	},
+
+	removeSharingOut: function() {
+		if (this._outFileList) {
+			this._outFileList.$fileList.empty();
+		}
 	},
 
 	_createFileActions: function() {
@@ -56,6 +70,7 @@ OCA.Sharing.App = {
 		var fileActions = new OCA.Files.FileActions();
 		// note: not merging the legacy actions because legacy apps are not
 		// compatible with the sharing overview and need to be adapted first
+		fileActions.registerDefaultActions();
 		fileActions.merge(OCA.Files.fileActions);
 
 		// when the user clicks on a folder, redirect to the corresponding
@@ -75,11 +90,17 @@ OCA.Sharing.App = {
 };
 
 $(document).ready(function() {
-	$('#app-content-sharingin').one('show', function(e) {
+	$('#app-content-sharingin').on('show', function(e) {
 		OCA.Sharing.App.initSharingIn($(e.target));
 	});
-	$('#app-content-sharingout').one('show', function(e) {
+	$('#app-content-sharingin').on('hide', function() {
+		OCA.Sharing.App.removeSharingIn();
+	});
+	$('#app-content-sharingout').on('show', function(e) {
 		OCA.Sharing.App.initSharingOut($(e.target));
 	});
+	$('#app-content-sharingout').on('hide', function() {
+		OCA.Sharing.App.removeSharingOut();
+	});
 });
 
diff --git a/apps/files_sharing/js/public.js b/apps/files_sharing/js/public.js
index 446f3f2..27e8d36 100644
--- a/apps/files_sharing/js/public.js
+++ b/apps/files_sharing/js/public.js
@@ -166,7 +166,10 @@ OCA.Sharing.PublicApp = {
 
 $(document).ready(function() {
 	var App = OCA.Sharing.PublicApp;
-	App.initialize($('#preview'));
+	// defer app init, to give a chance to plugins to register file actions
+	_.defer(function() {
+		App.initialize($('#preview'));
+	});
 
 	if (window.Files) {
 		// HACK: for oc-dialogs previews that depends on Files:
diff --git a/apps/files_sharing/js/share.js b/apps/files_sharing/js/share.js
index 1fcb1f0..5a42604 100644
--- a/apps/files_sharing/js/share.js
+++ b/apps/files_sharing/js/share.js
@@ -28,10 +28,11 @@ $(document).ready(function() {
 		}
 
 		// use delegate to catch the case with multiple file lists
-		$('#content').delegate('#fileList', 'fileActionsReady',function(){
+		$('#content').delegate('#fileList', 'fileActionsReady',function(ev){
 			// if no share action exists because the admin disabled sharing for this user
 			// we create a share notification action to inform the user about files
 			// shared with him otherwise we just update the existing share action.
+			var fileList = ev.fileList;
 			var $fileList = $(this);
 			$fileList.find('[data-share-owner]').each(function() {
 				var $tr = $(this);
@@ -59,16 +60,16 @@ $(document).ready(function() {
 						return $result;
 					});
 				}
-			})
+			});
 
 			if (!OCA.Sharing.sharesLoaded){
-				OC.Share.loadIcons('file', $fileList);
+				OC.Share.loadIcons('file', fileList);
 				// assume that we got all shares, so switching directories
 				// will not invalidate that list
 				OCA.Sharing.sharesLoaded = true;
 			}
 			else{
-				OC.Share.updateIcons('file', $fileList);
+				OC.Share.updateIcons('file', fileList);
 			}
 		});
 
diff --git a/apps/files_sharing/js/sharedfilelist.js b/apps/files_sharing/js/sharedfilelist.js
index 4508de4..b941722 100644
--- a/apps/files_sharing/js/sharedfilelist.js
+++ b/apps/files_sharing/js/sharedfilelist.js
@@ -43,10 +43,9 @@
 			var $tr = OCA.Files.FileList.prototype._createRow.apply(this, arguments);
 			$tr.find('.filesize').remove();
 			var $sharedWith = $('<td class="sharedWith"></td>')
-				.text(fileData.shareColumnInfo);
+				.text(fileData.counterParts.join(', '));
 			$tr.find('td.date').before($sharedWith);
 			$tr.find('td.filename input:checkbox').remove();
-			$tr.attr('data-path', fileData.path);
 			$tr.attr('data-share-id', _.pluck(fileData.shares, 'id').join(','));
 			return $tr;
 		},
@@ -74,7 +73,12 @@
 		},
 
 		getDirectoryPermissions: function() {
-			return OC.PERMISSION_READ;
+			return OC.PERMISSION_READ | OC.PERMISSION_DELETE;
+		},
+
+		updateStorageStatistics: function() {
+			// no op because it doesn't have
+			// storage info like free space / used space
 		},
 
 		reload: function() {
@@ -128,16 +132,16 @@
 			var self = this;
 			// OCS API uses non-camelcased names
 			var files = _.chain(data)
-				// cOnvert share data to file data
+				// convert share data to file data
 				.map(function(share) {
 					/* jshint camelcase: false */
 					var file = {
 						id: share.file_source,
-						mtime: share.stime * 1000,
 						mimetype: share.mimetype
 					};
 					if (share.item_type === 'folder') {
 						file.type = 'dir';
+						file.mimetype = 'httpd/unix-directory';
 					}
 					else {
 						file.type = 'file';
@@ -150,7 +154,8 @@
 					file.share = {
 						id: share.id,
 						type: share.share_type,
-						target: share.share_with
+						target: share.share_with,
+						stime: share.stime * 1000,
 					};
 					if (self._sharedWithUser) {
 						file.share.ownerDisplayName = share.displayname_owner;
@@ -173,28 +178,49 @@
 				// inside the same file object (by file id).
 				.reduce(function(memo, file) {
 					var data = memo[file.id];
+					var counterPart = file.share.ownerDisplayName || file.share.targetDisplayName;
 					if (!data) {
 						data = memo[file.id] = file;
 						data.shares = [file.share];
+						// using a hash to make them unique,
+						// this is only a list to be displayed
+						data.counterParts = {};
+						// counter is cheaper than calling _.keys().length
+						data.counterPartsCount = 0;
+						data.mtime = file.share.stime;
 					}
 					else {
+						// always take the most recent stime
+						if (file.share.stime > data.mtime) {
+							data.mtime = file.share.stime;
+						}
 						data.shares.push(file.share);
 					}
-					// format the share column info output string
-					if (!data.shareColumnInfo) {
-						data.shareColumnInfo = '';
-					}
-					else {
-						data.shareColumnInfo += ', ';
+
+					if (file.share.type === OC.Share.SHARE_TYPE_LINK) {
+						data.hasLinkShare = true;
+					} else if (counterPart && data.counterPartsCount < 10) {
+						// limit counterparts for output
+						data.counterParts[counterPart] = true;
+						data.counterPartsCount++;
 					}
-					// TODO. more accurate detection of name based on type
-					// TODO: maybe better formatting, like "link + 3 users" when more than 1 user
-					data.shareColumnInfo += (file.share.ownerDisplayName || file.share.targetDisplayName || 'link');
+
 					delete file.share;
 					return memo;
 				}, {})
 				// Retrieve only the values of the returned hash
 				.values()
+				// Clean up
+				.each(function(data) {
+					// convert the counterParts map to a flat
+					// array of sorted names
+					data.counterParts = _.chain(data.counterParts).keys().sort().value();
+					if (data.hasLinkShare) {
+						data.counterParts.unshift(t('files_sharing', 'link'));
+						delete data.hasLinkShare;
+					}
+					delete data.counterPartsCount;
+				})
 				// Sort by expected sort comparator
 				.sortBy(this._sortComparator)
 				// Finish the chain by getting the result
diff --git a/apps/files_sharing/templates/list.php b/apps/files_sharing/templates/list.php
index c688dcf..b07222c 100644
--- a/apps/files_sharing/templates/list.php
+++ b/apps/files_sharing/templates/list.php
@@ -16,11 +16,11 @@
 					<a class="name sort columntitle" data-sort="name"><span><?php p($l->t( 'Name' )); ?></span><span class="sort-indicator"></span></a>
 				</div>
 			</th>
-			<th id="headerSharedWith" class="hidden column-mtime">
+			<th id="headerSharedWith" class="hidden column-counterpart">
 				<a id="sharedwith" class="columntitle" data-sort="shareWith"><span><?php p($l->t( 'Shared with' )); ?></span><span class="sort-indicator"></span></a>
 			</th>
 			<th id="headerDate" class="hidden column-mtime">
-				<a id="modified" class="columntitle" data-sort="mtime"><span><?php p($l->t( 'Shared since' )); ?></span><span class="sort-indicator"></span></a>
+				<a id="modified" class="columntitle" data-sort="mtime"><span><?php p($l->t( 'Share time' )); ?></span><span class="sort-indicator"></span></a>
 			</th>
 		</tr>
 	</thead>
diff --git a/apps/files_sharing/tests/js/appSpec.js b/apps/files_sharing/tests/js/appSpec.js
new file mode 100644
index 0000000..09c48a6
--- /dev/null
+++ b/apps/files_sharing/tests/js/appSpec.js
@@ -0,0 +1,140 @@
+/**
+* ownCloud
+*
+* @author Vincent Petry
+* @copyright 2014 Vincent Petry <pvince81 at owncloud.com>
+*
+* This library is free software; you can redistribute it and/or
+* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+* License as published by the Free Software Foundation; either
+* version 3 of the License, or any later version.
+*
+* This library 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 library.  If not, see <http://www.gnu.org/licenses/>.
+*
+*/
+
+describe('OCA.Sharing.App tests', function() {
+	var App = OCA.Sharing.App;
+	var fileListIn;
+	var fileListOut;
+
+	beforeEach(function() {
+		$('#testArea').append(
+			'<div id="app-navigation">' +
+			'<ul><li data-id="files"><a>Files</a></li>' +
+			'<li data-id="sharingin"><a></a></li>' +
+			'<li data-id="sharingout"><a></a></li>' +
+			'</ul></div>' +
+			'<div id="app-content">' +
+			'<div id="app-content-files" class="hidden">' +
+			'</div>' +
+			'<div id="app-content-sharingin" class="hidden">' +
+			'</div>' +
+			'<div id="app-content-sharingout" class="hidden">' +
+			'</div>' +
+			'</div>' +
+			'</div>'
+		);
+		fileListIn = App.initSharingIn($('#app-content-sharingin'));
+		fileListOut = App.initSharingOut($('#app-content-sharingout'));
+	});
+	afterEach(function() {
+		App._inFileList = null;
+		App._outFileList = null;
+		fileListIn = null;
+		fileListOut = null;
+	});
+
+	describe('initialization', function() {
+		it('inits sharing-in list on show', function() {
+			expect(fileListIn._sharedWithUser).toEqual(true);		
+		});
+		it('inits sharing-out list on show', function() {
+			expect(fileListOut._sharedWithUser).toBeFalsy();
+		});
+	});
+	describe('file actions', function() {
+		it('provides default file actions', function() {
+			_.each([fileListIn, fileListOut], function(fileList) {
+				var fileActions = fileList.fileActions;
+
+				expect(fileActions.actions.all).toBeDefined();
+				expect(fileActions.actions.all.Delete).toBeDefined();
+				expect(fileActions.actions.all.Rename).toBeDefined();
+				expect(fileActions.actions.file.Download).toBeDefined();
+
+				expect(fileActions.defaults.dir).toEqual('Open');
+			});
+		});
+		it('provides custom file actions', function() {
+			var actionStub = sinon.stub();
+			// regular file action
+			OCA.Files.fileActions.register(
+					'all',
+					'RegularTest',
+					OC.PERMISSION_READ,
+					OC.imagePath('core', 'actions/shared'),
+					actionStub
+			);
+
+			App._inFileList = null;
+			fileListIn = App.initSharingIn($('#app-content-sharingin'));
+
+			expect(fileListIn.fileActions.actions.all.RegularTest).toBeDefined();
+		});
+		it('does not provide legacy file actions', function() {
+			var actionStub = sinon.stub();
+			// legacy file action
+			window.FileActions.register(
+					'all',
+					'LegacyTest',
+					OC.PERMISSION_READ,
+					OC.imagePath('core', 'actions/shared'),
+					actionStub
+			);
+
+			App._inFileList = null;
+			fileListIn = App.initSharingIn($('#app-content-sharingin'));
+
+			expect(fileListIn.fileActions.actions.all.LegacyTest).not.toBeDefined();
+		});
+		it('redirects to files app when opening a directory', function() {
+			var oldList = OCA.Files.App.fileList;
+			// dummy new list to make sure it exists
+			OCA.Files.App.fileList = new OCA.Files.FileList($('<table><thead></thead><tbody></tbody></table>'));
+
+			var setActiveViewStub = sinon.stub(OCA.Files.App, 'setActiveView');
+			// create dummy table so we can click the dom
+			var $table = '<table><thead></thead><tbody id="fileList"></tbody></table>';
+			$('#app-content-sharingin').append($table);
+
+			App._inFileList = null;
+			fileListIn = App.initSharingIn($('#app-content-sharingin'));
+
+			fileListIn.setFiles([{
+				name: 'testdir',
+				type: 'dir',
+				path: '/somewhere/inside/subdir',
+				counterParts: ['user2']
+			}]);
+
+			fileListIn.findFileEl('testdir').find('td a.name').click();
+
+			expect(OCA.Files.App.fileList.getCurrentDirectory()).toEqual('/somewhere/inside/subdir/testdir');
+
+			expect(setActiveViewStub.calledOnce).toEqual(true);
+			expect(setActiveViewStub.calledWith('files')).toEqual(true);
+
+			setActiveViewStub.restore();
+
+			// restore old list
+			OCA.Files.App.fileList = oldList;
+		});
+	});
+});
diff --git a/apps/files_sharing/tests/js/sharedfilelistSpec.js b/apps/files_sharing/tests/js/sharedfilelistSpec.js
new file mode 100644
index 0000000..ddcd746
--- /dev/null
+++ b/apps/files_sharing/tests/js/sharedfilelistSpec.js
@@ -0,0 +1,417 @@
+/*
+ * Copyright (c) 2014 Vincent Petry <pvince81 at owncloud.com>
+ *
+ * This file is licensed under the Affero General Public License version 3
+ * or later.
+ *
+ * See the COPYING-README file.
+ *
+ */
+
+describe('OCA.Sharing.FileList tests', function() {
+	var testFiles, alertStub, notificationStub, fileList;
+
+	beforeEach(function() {
+		alertStub = sinon.stub(OC.dialogs, 'alert');
+		notificationStub = sinon.stub(OC.Notification, 'show');
+
+		// init parameters and test table elements
+		$('#testArea').append(
+			'<div id="app-content-container">' +
+			// init horrible parameters
+			'<input type="hidden" id="dir" value="/"></input>' +
+			'<input type="hidden" id="permissions" value="31"></input>' +
+			// dummy controls
+			'<div id="controls">' +
+			'   <div class="actions creatable"></div>' +
+			'   <div class="notCreatable"></div>' +
+			'</div>' +
+			// dummy table
+			// TODO: at some point this will be rendered by the fileList class itself!
+			'<table id="filestable">' +
+			'<thead><tr>' +
+			'<th id="headerName" class="hidden column-name">' +
+			'<input type="checkbox" id="select_all_files" class="select-all">' +
+			'<a class="name columntitle" data-sort="name"><span>Name</span><span class="sort-indicator"></span></a>' +
+			'<span class="selectedActions hidden">' +
+			'</th>' +
+			'<th class="hidden column-mtime">' +
+			'<a class="columntitle" data-sort="mtime"><span class="sort-indicator"></span></a>' +
+			'</th>' +
+			'</tr></thead>' +
+			'<tbody id="fileList"></tbody>' +
+			'<tfoot></tfoot>' +
+			'</table>' +
+			'<div id="emptycontent">Empty content message</div>' +
+			'</div>'
+		);
+	});
+	afterEach(function() {
+		testFiles = undefined;
+		fileList = undefined;
+
+		notificationStub.restore();
+		alertStub.restore();
+	});
+
+	describe('loading file list for incoming shares', function() {
+		var ocsResponse;
+
+		beforeEach(function() {
+			fileList = new OCA.Sharing.FileList(
+				$('#app-content-container'), {
+					sharedWithUser: true
+				}
+			);
+
+			fileList.reload();
+
+			/* jshint camelcase: false */
+			ocsResponse = {
+				ocs: {
+					meta: {
+						status: 'ok',
+						statuscode: 100,
+						message: null
+					},
+					data: [{
+						id: 7,
+						item_type: 'file',
+						item_source: 49,
+						item_target: '/49',
+						file_source: 49,
+						file_target: '/local path/local name.txt',
+						path: 'files/something shared.txt',
+						permissions: 31,
+						stime: 11111,
+						share_type: OC.Share.SHARE_TYPE_USER,
+						share_with: 'user1',
+						share_with_displayname: 'User One',
+						mimetype: 'text/plain',
+						uid_owner: 'user2',
+						displayname_owner: 'User Two'
+					}]
+				}
+			};
+		});
+		it('render file shares', function() {
+			var request;
+
+			expect(fakeServer.requests.length).toEqual(1);
+			request = fakeServer.requests[0];
+			expect(request.url).toEqual(
+				OC.linkToOCS('apps/files_sharing/api/v1') +
+				'shares?format=json&shared_with_me=true'
+			);
+
+			fakeServer.requests[0].respond(
+				200,
+				{ 'Content-Type': 'application/json' },
+				JSON.stringify(ocsResponse)
+			);
+
+			var $rows = fileList.$el.find('tbody tr');
+			var $tr = $rows.eq(0);
+			expect($rows.length).toEqual(1);
+			expect($tr.attr('data-id')).toEqual('49');
+			expect($tr.attr('data-type')).toEqual('file');
+			expect($tr.attr('data-file')).toEqual('local name.txt');
+			expect($tr.attr('data-path')).toEqual('/local path');
+			expect($tr.attr('data-size')).not.toBeDefined();
+			expect($tr.attr('data-permissions')).toEqual('31'); // read and delete
+			expect($tr.attr('data-mime')).toEqual('text/plain');
+			expect($tr.attr('data-mtime')).toEqual('11111000');
+			expect($tr.attr('data-share-id')).toEqual('7');
+			expect($tr.find('a.name').attr('href')).toEqual(
+				OC.webroot +
+				'/index.php/apps/files/ajax/download.php' +
+				'?dir=%2Flocal%20path&files=local%20name.txt'
+			);
+			expect($tr.find('td.sharedWith').text()).toEqual('User Two');
+
+			expect($tr.find('.nametext').text().trim()).toEqual('local name.txt');
+		});
+		it('render folder shares', function() {
+			/* jshint camelcase: false */
+			var request;
+			ocsResponse.ocs.data[0] = _.extend(ocsResponse.ocs.data[0], {
+				item_type: 'folder',
+				file_target: '/local path/local name',
+				path: 'files/something shared',
+			});
+
+			expect(fakeServer.requests.length).toEqual(1);
+			request = fakeServer.requests[0];
+			expect(request.url).toEqual(
+				OC.linkToOCS('apps/files_sharing/api/v1') +
+				'shares?format=json&shared_with_me=true'
+			);
+
+			fakeServer.requests[0].respond(
+				200,
+				{ 'Content-Type': 'application/json' },
+				JSON.stringify(ocsResponse)
+			);
+
+			var $rows = fileList.$el.find('tbody tr');
+			var $tr = $rows.eq(0);
+			expect($rows.length).toEqual(1);
+			expect($tr.attr('data-id')).toEqual('49');
+			expect($tr.attr('data-type')).toEqual('dir');
+			expect($tr.attr('data-file')).toEqual('local name');
+			expect($tr.attr('data-path')).toEqual('/local path');
+			expect($tr.attr('data-size')).not.toBeDefined();
+			expect($tr.attr('data-permissions')).toEqual('31'); // read and delete
+			expect($tr.attr('data-mime')).toEqual('httpd/unix-directory');
+			expect($tr.attr('data-mtime')).toEqual('11111000');
+			expect($tr.attr('data-share-id')).toEqual('7');
+			expect($tr.find('a.name').attr('href')).toEqual(
+				OC.webroot +
+				'/index.php/apps/files' +
+				'?dir=/local%20path/local%20name'
+			);
+			expect($tr.find('td.sharedWith').text()).toEqual('User Two');
+
+			expect($tr.find('.nametext').text().trim()).toEqual('local name');
+		});
+	});
+	describe('loading file list for outgoing shares', function() {
+		var ocsResponse;
+
+		beforeEach(function() {
+			fileList = new OCA.Sharing.FileList(
+				$('#app-content-container'), {
+					sharedWithUser: false
+				}
+			);
+
+			fileList.reload();
+
+			/* jshint camelcase: false */
+			ocsResponse = {
+				ocs: {
+					meta: {
+						status: 'ok',
+						statuscode: 100,
+						message: null
+					},
+					data: [{
+						id: 7,
+						item_type: 'file',
+						item_source: 49,
+						file_source: 49,
+						path: '/local path/local name.txt',
+						permissions: 27,
+						stime: 11111,
+						share_type: OC.Share.SHARE_TYPE_USER,
+						share_with: 'user2',
+						share_with_displayname: 'User Two',
+						mimetype: 'text/plain',
+						uid_owner: 'user1',
+						displayname_owner: 'User One'
+					}]
+				}
+			};
+		});
+		it('render file shares', function() {
+			var request;
+
+			expect(fakeServer.requests.length).toEqual(1);
+			request = fakeServer.requests[0];
+			expect(request.url).toEqual(
+				OC.linkToOCS('apps/files_sharing/api/v1') +
+				'shares?format=json&shared_with_me=false'
+			);
+
+			fakeServer.requests[0].respond(
+				200,
+				{ 'Content-Type': 'application/json' },
+				JSON.stringify(ocsResponse)
+			);
+
+			var $rows = fileList.$el.find('tbody tr');
+			var $tr = $rows.eq(0);
+			expect($rows.length).toEqual(1);
+			expect($tr.attr('data-id')).toEqual('49');
+			expect($tr.attr('data-type')).toEqual('file');
+			expect($tr.attr('data-file')).toEqual('local name.txt');
+			expect($tr.attr('data-path')).toEqual('/local path');
+			expect($tr.attr('data-size')).not.toBeDefined();
+			expect($tr.attr('data-permissions')).toEqual('31'); // read and delete
+			expect($tr.attr('data-mime')).toEqual('text/plain');
+			expect($tr.attr('data-mtime')).toEqual('11111000');
+			expect($tr.attr('data-share-id')).toEqual('7');
+			expect($tr.find('a.name').attr('href')).toEqual(
+				OC.webroot +
+				'/index.php/apps/files/ajax/download.php' +
+				'?dir=%2Flocal%20path&files=local%20name.txt'
+			);
+			expect($tr.find('td.sharedWith').text()).toEqual('User Two');
+
+			expect($tr.find('.nametext').text().trim()).toEqual('local name.txt');
+		});
+		it('render folder shares', function() {
+			var request;
+			/* jshint camelcase: false */
+			ocsResponse.ocs.data[0] = _.extend(ocsResponse.ocs.data[0], {
+				item_type: 'folder',
+				path: '/local path/local name',
+			});
+
+			expect(fakeServer.requests.length).toEqual(1);
+			request = fakeServer.requests[0];
+			expect(request.url).toEqual(
+				OC.linkToOCS('apps/files_sharing/api/v1') +
+				'shares?format=json&shared_with_me=false'
+			);
+
+			fakeServer.requests[0].respond(
+				200,
+				{ 'Content-Type': 'application/json' },
+				JSON.stringify(ocsResponse)
+			);
+
+			var $rows = fileList.$el.find('tbody tr');
+			var $tr = $rows.eq(0);
+			expect($rows.length).toEqual(1);
+			expect($tr.attr('data-id')).toEqual('49');
+			expect($tr.attr('data-type')).toEqual('dir');
+			expect($tr.attr('data-file')).toEqual('local name');
+			expect($tr.attr('data-path')).toEqual('/local path');
+			expect($tr.attr('data-size')).not.toBeDefined();
+			expect($tr.attr('data-permissions')).toEqual('31'); // read and delete
+			expect($tr.attr('data-mime')).toEqual('httpd/unix-directory');
+			expect($tr.attr('data-mtime')).toEqual('11111000');
+			expect($tr.attr('data-share-id')).toEqual('7');
+			expect($tr.find('a.name').attr('href')).toEqual(
+				OC.webroot +
+				'/index.php/apps/files' +
+				'?dir=/local%20path/local%20name'
+			);
+			expect($tr.find('td.sharedWith').text()).toEqual('User Two');
+
+			expect($tr.find('.nametext').text().trim()).toEqual('local name');
+		});
+		it('render link shares', function() {
+			/* jshint camelcase: false */
+			var request;
+			ocsResponse.ocs.data[0] = {
+				id: 7,
+				item_type: 'file',
+				item_source: 49,
+				file_source: 49,
+				path: '/local path/local name.txt',
+				permissions: 1,
+				stime: 11111,
+				share_type: OC.Share.SHARE_TYPE_LINK,
+				share_with: null,
+				token: 'abc',
+				mimetype: 'text/plain',
+				uid_owner: 'user1',
+				displayname_owner: 'User One'
+			};
+			expect(fakeServer.requests.length).toEqual(1);
+			request = fakeServer.requests[0];
+			expect(request.url).toEqual(
+				OC.linkToOCS('apps/files_sharing/api/v1') +
+				'shares?format=json&shared_with_me=false'
+			);
+
+			fakeServer.requests[0].respond(
+				200,
+				{ 'Content-Type': 'application/json' },
+				JSON.stringify(ocsResponse)
+			);
+
+			var $rows = fileList.$el.find('tbody tr');
+			var $tr = $rows.eq(0);
+			expect($rows.length).toEqual(1);
+			expect($tr.attr('data-id')).toEqual('49');
+			expect($tr.attr('data-type')).toEqual('file');
+			expect($tr.attr('data-file')).toEqual('local name.txt');
+			expect($tr.attr('data-path')).toEqual('/local path');
+			expect($tr.attr('data-size')).not.toBeDefined();
+			expect($tr.attr('data-permissions')).toEqual('31'); // read and delete
+			expect($tr.attr('data-mime')).toEqual('text/plain');
+			expect($tr.attr('data-mtime')).toEqual('11111000');
+			expect($tr.attr('data-share-id')).toEqual('7');
+			expect($tr.find('a.name').attr('href')).toEqual(
+					OC.webroot +
+					'/index.php/apps/files/ajax/download.php' +
+					'?dir=%2Flocal%20path&files=local%20name.txt');
+			expect($tr.find('td.sharedWith').text()).toEqual('link');
+
+			expect($tr.find('.nametext').text().trim()).toEqual('local name.txt');
+		});
+		it('groups link shares with regular shares', function() {
+			/* jshint camelcase: false */
+			var request;
+			// link share
+			ocsResponse.ocs.data.push({
+				id: 8,
+				item_type: 'file',
+				item_source: 49,
+				file_source: 49,
+				path: '/local path/local name.txt',
+				permissions: 1,
+				stime: 11111,
+				share_type: OC.Share.SHARE_TYPE_LINK,
+				share_with: null,
+				token: 'abc',
+				mimetype: 'text/plain',
+				uid_owner: 'user1',
+				displayname_owner: 'User One'
+			});
+			// another share of the same file
+			ocsResponse.ocs.data.push({
+				id: 9,
+				item_type: 'file',
+				item_source: 49,
+				file_source: 49,
+				path: '/local path/local name.txt',
+				permissions: 27,
+				stime: 22222,
+				share_type: OC.Share.SHARE_TYPE_USER,
+				share_with: 'user3',
+				share_with_displayname: 'User Three',
+				mimetype: 'text/plain',
+				uid_owner: 'user1',
+				displayname_owner: 'User One'
+			});
+			expect(fakeServer.requests.length).toEqual(1);
+			request = fakeServer.requests[0];
+			expect(request.url).toEqual(
+				OC.linkToOCS('apps/files_sharing/api/v1') +
+				'shares?format=json&shared_with_me=false'
+			);
+
+			fakeServer.requests[0].respond(
+				200,
+				{ 'Content-Type': 'application/json' },
+				JSON.stringify(ocsResponse)
+			);
+
+			var $rows = fileList.$el.find('tbody tr');
+			var $tr = $rows.eq(0);
+			expect($rows.length).toEqual(1);
+			expect($tr.attr('data-id')).toEqual('49');
+			expect($tr.attr('data-type')).toEqual('file');
+			expect($tr.attr('data-file')).toEqual('local name.txt');
+			expect($tr.attr('data-path')).toEqual('/local path');
+			expect($tr.attr('data-size')).not.toBeDefined();
+			expect($tr.attr('data-permissions')).toEqual('31'); // read and delete
+			expect($tr.attr('data-mime')).toEqual('text/plain');
+			// always use the most recent stime
+			expect($tr.attr('data-mtime')).toEqual('22222000');
+			expect($tr.attr('data-share-id')).toEqual('7,8,9');
+			expect($tr.find('a.name').attr('href')).toEqual(
+				OC.webroot +
+				'/index.php/apps/files/ajax/download.php' +
+				'?dir=%2Flocal%20path&files=local%20name.txt'
+			);
+			expect($tr.find('td.sharedWith').text()).toEqual('link, User Three, User Two');
+
+			expect($tr.find('.nametext').text().trim()).toEqual('local name.txt');
+		});
+	});
+});
diff --git a/core/js/share.js b/core/js/share.js
index 279b1d1..894f0d4 100644
--- a/core/js/share.js
+++ b/core/js/share.js
@@ -32,27 +32,26 @@ OC.Share={
 	 * (not reloaded from server)
 	 *
 	 * @param itemType item type
-	 * @param fileList file list instance or file list jQuery element,
+	 * @param fileList file list instance
 	 * defaults to OCA.Files.App.fileList
 	 */
 	updateIcons:function(itemType, fileList){
 		var item;
-		var $fileList = (fileList || OCA.Files.App.fileList);
-		// in case the jQuery element was passed instead
-		if ($fileList.$fileList) {
-			$fileList = $fileList.$fileList;
-		}
+		fileList = fileList || OCA.Files.App.fileList;
+		var $fileList = fileList.$fileList;
+		var currentDir = fileList.getCurrentDirectory();
 		for (item in OC.Share.statuses){
+			var image;
 			var data = OC.Share.statuses[item];
 
-			var hasLink = data['link'];
+			var hasLink = data.link;
 			// Links override shared in terms of icon display
 			if (hasLink) {
-				var image = OC.imagePath('core', 'actions/public');
+				image = OC.imagePath('core', 'actions/public');
 			} else {
-				var image = OC.imagePath('core', 'actions/shared');
+				image = OC.imagePath('core', 'actions/shared');
 			}
-			if (itemType != 'file' && itemType != 'folder') {
+			if (itemType !== 'file' && itemType !== 'folder') {
 				$fileList.find('a.share[data-item="'+item+'"]').css('background', 'url('+image+') no-repeat center');
 			} else {
 				var file = $fileList.find('tr[data-id="'+item+'"]');
@@ -62,17 +61,17 @@ OC.Share={
 					action.addClass('permanent');
 					action.html(' <span>'+t('core', 'Shared')+'</span>').prepend(img);
 				} else {
-					var dir = $('#dir').val();
+					var dir = currentDir;
 					if (dir.length > 1) {
 						var last = '';
 						var path = dir;
 						// Search for possible parent folders that are shared
 						while (path != last) {
-							if (path == data['path'] && !data['link']) {
+							if (path === data.path && !data.link) {
 								var actions = $fileList.find('.fileactions .action[data-action="Share"]');
 								$.each(actions, function(index, action) {
 									var img = $(action).find('img');
-									if (img.attr('src') != OC.imagePath('core', 'actions/public')) {
+									if (img.attr('src') !== OC.imagePath('core', 'actions/public')) {
 										img.attr('src', image);
 										$(action).addClass('permanent');
 										$(action).html(' <span>'+t('core', 'Shared')+'</span>').prepend(img);
@@ -112,14 +111,18 @@ OC.Share={
 			var file = $('tr').filterAttr('data-id', String(itemSource));
 			if (file.length > 0) {
 				var action = $(file).find('.fileactions .action').filterAttr('data-action', 'Share');
-				var img = action.find('img').attr('src', image);
-				if (shares) {
-					action.addClass('permanent');
-					action.html(' <span>'+ escapeHTML(t('core', 'Shared'))+'</span>').prepend(img);
-				} else {
-					action.removeClass('permanent');
-					action.html(' <span>'+ escapeHTML(t('core', 'Share'))+'</span>').prepend(img);
-				}
+				// in case of multiple lists/rows, there might be more than one visible
+				action.each(function() {
+					var action = $(this);
+					var img = action.find('img').attr('src', image);
+					if (shares) {
+						action.addClass('permanent');
+						action.html(' <span>'+ escapeHTML(t('core', 'Shared'))+'</span>').prepend(img);
+					} else {
+						action.removeClass('permanent');
+						action.html(' <span>'+ escapeHTML(t('core', 'Share'))+'</span>').prepend(img);
+					}
+				});
 			}
 		}
 		if (shares) {
diff --git a/tests/karma.config.js b/tests/karma.config.js
index 08b49d8..846e8f7 100644
--- a/tests/karma.config.js
+++ b/tests/karma.config.js
@@ -43,7 +43,19 @@ module.exports = function(config) {
 		return apps;
 		*/
 		// other apps tests don't run yet... needs further research / clean up
-		return ['files', 'files_trashbin'];
+		return [
+			'files',
+			'files_trashbin',
+			{
+				name: 'files_sharing',
+				srcFiles: [
+					// only test these files, others are not ready and mess
+					// up with the global namespace/classes/state
+					'apps/files_sharing/js/app.js',
+					'apps/files_sharing/js/sharedfilelist.js'
+				],
+				testFiles: ['apps/files_sharing/tests/js/*.js']
+			}];
 	}
 
 	// respect NOCOVERAGE env variable
@@ -110,15 +122,30 @@ module.exports = function(config) {
 		files.push(corePath + 'tests/specs/*.js');
 	}
 
-	for ( var i = 0; i < appsToTest.length; i++ ) {
-		// add app JS
-		var srcFile = 'apps/' + appsToTest[i] + '/js/*.js';
-		files.push(srcFile);
+	function addApp(app) {
+		// if only a string was specified, expand to structure
+		if (typeof(app) === 'string') {
+			app = {
+				srcFiles: 'apps/' + app + '/js/*.js',
+				testFiles: 'apps/' + app + '/tests/js/*.js'
+			};
+		}
+
+		// add source files/patterns
+		files = files.concat(app.srcFiles || []);
+		// add test files/patterns
+		files = files.concat(app.testFiles || []);
 		if (enableCoverage) {
-			preprocessors[srcFile] = 'coverage';
+			// add coverage entry for each file/pattern
+			for (var i = 0; i < app.srcFiles.length; i++) {
+				preprocessors[app.srcFiles[i]] = 'coverage';
+			}
 		}
-		// add test specs
-		files.push('apps/' + appsToTest[i] + '/tests/js/*.js');
+	}
+
+	// add source files for apps to test
+	for ( var i = 0; i < appsToTest.length; i++ ) {
+		addApp(appsToTest[i]);
 	}
 
 	// serve images to avoid warnings

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-owncloud/owncloud.git



More information about the Pkg-owncloud-commits mailing list