Merge pull request #18748 from owncloud/files-versions-tab

Add versions tab to files sidebar
This commit is contained in:
Morris Jobke 2015-09-07 00:53:57 +02:00
commit f2ca0f6677
18 changed files with 976 additions and 221 deletions

View File

@ -35,7 +35,7 @@
var DetailsView = OC.Backbone.View.extend({
id: 'app-sidebar',
tabName: 'div',
className: 'detailsView',
className: 'detailsView scroll-container',
_template: null,

View File

@ -84,6 +84,13 @@
*/
getFileInfo: function() {
return this.model;
},
/**
* Load the next page of results
*/
nextPage: function() {
// load the next page, if applicable
}
});
DetailTabView._TAB_COUNT = 0;

View File

@ -291,6 +291,7 @@
* @return {OCA.Files.FileInfoModel} file info model
*/
getModelForFile: function(fileName) {
var self = this;
var $tr;
// jQuery object ?
if (fileName.is) {
@ -318,6 +319,21 @@
if (!model.has('path')) {
model.set('path', this.getCurrentDirectory(), {silent: true});
}
model.on('change', function(model) {
// re-render row
var highlightState = $tr.hasClass('highlighted');
$tr = self.updateRow(
$tr,
_.extend({isPreviewAvailable: true}, model.toJSON()),
{updateSummary: true, silent: false, animate: true}
);
$tr.toggleClass('highlighted', highlightState);
});
model.on('busy', function(model, state) {
self.showFileBusyState($tr, state);
});
return model;
},
@ -341,6 +357,9 @@
if (!fileName) {
OC.Apps.hideAppSidebar(this._detailsView.$el);
this._detailsView.setFileInfo(null);
if (this._currentFileModel) {
this._currentFileModel.off();
}
this._currentFileModel = null;
return;
}
@ -1223,6 +1242,10 @@
reload: function() {
this._selectedFiles = {};
this._selectionSummary.clear();
if (this._currentFileModel) {
this._currentFileModel.off();
}
this._currentFileModel = null;
this.$el.find('.select-all').prop('checked', false);
this.showMask();
if (this._reloadCall) {
@ -1554,6 +1577,23 @@
},
/**
* Updates the given row with the given file info
*
* @param {Object} $tr row element
* @param {OCA.Files.FileInfo} fileInfo file info
* @param {Object} options options
*
* @return {Object} new row element
*/
updateRow: function($tr, fileInfo, options) {
this.files.splice($tr.index(), 1);
$tr.remove();
$tr = this.add(fileInfo, _.extend({updateSummary: false, silent: true}, options));
this.$fileList.trigger($.Event('fileActionsReady', {fileList: this, $files: $tr}));
return $tr;
},
/**
* Triggers file rename input field for the given file name.
* If the user enters a new name, the file will be renamed.

View File

@ -92,6 +92,7 @@
actionHandler: function(fileName, context) {
var $actionEl = context.$file.find('.action-favorite');
var $file = context.$file;
var fileInfo = context.fileList.files[$file.index()];
var dir = context.dir || context.fileList.getCurrentDirectory();
var tags = $file.attr('data-tags');
if (_.isUndefined(tags)) {
@ -106,9 +107,11 @@
} else {
tags.push(OC.TAG_FAVORITE);
}
// pre-toggle the star
toggleStar($actionEl, !isFavorite);
context.fileInfoModel.set('tags', tags);
context.fileInfoModel.trigger('busy', context.fileInfoModel, true);
self.applyFileTags(
dir + '/' + fileName,
@ -116,17 +119,16 @@
$actionEl,
isFavorite
).then(function(result) {
context.fileInfoModel.trigger('busy', context.fileInfoModel, false);
// response from server should contain updated tags
var newTags = result.tags;
if (_.isUndefined(newTags)) {
newTags = tags;
}
var fileInfo = context.fileList.files[$file.index()];
// read latest state from result
toggleStar($actionEl, (newTags.indexOf(OC.TAG_FAVORITE) >= 0));
$file.attr('data-tags', newTags.join('|'));
$file.attr('data-favorite', !isFavorite);
fileInfo.tags = newTags;
context.fileInfoModel.set({
'tags': newTags,
'favorite': !isFavorite
});
});
}
});

View File

@ -79,12 +79,12 @@ describe('OCA.Files.TagsPlugin tests', function() {
it('sends request to server and updates icon', function() {
var request;
fileList.setFiles(testFiles);
$tr = fileList.$el.find('tbody tr:first');
$action = $tr.find('.action-favorite');
var $tr = fileList.findFileEl('One.txt');
var $action = $tr.find('.action-favorite');
$action.click();
expect(fakeServer.requests.length).toEqual(1);
var request = fakeServer.requests[0];
request = fakeServer.requests[0];
expect(JSON.parse(request.requestBody)).toEqual({
tags: ['tag1', 'tag2', OC.TAG_FAVORITE]
});
@ -92,12 +92,18 @@ describe('OCA.Files.TagsPlugin tests', function() {
tags: ['tag1', 'tag2', 'tag3', OC.TAG_FAVORITE]
}));
// re-read the element as it was re-inserted
$tr = fileList.findFileEl('One.txt');
$action = $tr.find('.action-favorite');
expect($tr.attr('data-favorite')).toEqual('true');
expect($tr.attr('data-tags').split('|')).toEqual(['tag1', 'tag2', 'tag3', OC.TAG_FAVORITE]);
expect(fileList.files[0].tags).toEqual(['tag1', 'tag2', 'tag3', OC.TAG_FAVORITE]);
expect($action.find('img').attr('src')).toEqual(OC.imagePath('core', 'actions/starred'));
$action.click();
expect(fakeServer.requests.length).toEqual(2);
request = fakeServer.requests[1];
expect(JSON.parse(request.requestBody)).toEqual({
tags: ['tag1', 'tag2', 'tag3']
@ -106,7 +112,11 @@ describe('OCA.Files.TagsPlugin tests', function() {
tags: ['tag1', 'tag2', 'tag3']
}));
expect($tr.attr('data-favorite')).toEqual('false');
// re-read the element as it was re-inserted
$tr = fileList.findFileEl('One.txt');
$action = $tr.find('.action-favorite');
expect($tr.attr('data-favorite')).toBeFalsy();
expect($tr.attr('data-tags').split('|')).toEqual(['tag1', 'tag2', 'tag3']);
expect(fileList.files[0].tags).toEqual(['tag1', 'tag2', 'tag3']);
expect($action.find('img').attr('src')).toEqual(OC.imagePath('core', 'actions/star'));

View File

@ -44,6 +44,6 @@ if( $versions ) {
} else {
\OCP\JSON::success(array('data' => array('versions' => false, 'endReached' => true)));
\OCP\JSON::success(array('data' => array('versions' => [], 'endReached' => true)));
}

View File

@ -53,7 +53,10 @@ try {
$preview->setScalingUp($scalingUp);
$preview->showPreview();
}catch(\Exception $e) {
} catch (\OCP\Files\NotFoundException $e) {
\OC_Response::setStatus(404);
\OCP\Util::writeLog('core', $e->getmessage(), \OCP\Util::DEBUG);
} catch (\Exception $e) {
\OC_Response::setStatus(500);
\OCP\Util::writeLog('core', $e->getmessage(), \OCP\Util::DEBUG);
}

View File

@ -1,19 +1,18 @@
#dropdown.drop-versions {
width: 360px;
.versionsTabView .clear-float {
clear: both;
}
#found_versions li {
.versionsTabView li {
width: 100%;
cursor: default;
height: 56px;
float: left;
border-bottom: 1px solid rgba(100,100,100,.1);
}
#found_versions li:last-child {
.versionsTabView li:last-child {
border-bottom: none;
}
#found_versions li > * {
.versionsTabView li > * {
padding: 7px;
float: left;
vertical-align: top;
@ -22,34 +21,34 @@
opacity: .5;
}
#found_versions li > a,
#found_versions li > span {
.versionsTabView li > a,
.versionsTabView li > span {
padding: 17px 7px;
}
#found_versions li > *:hover,
#found_versions li > *:focus {
.versionsTabView li > *:hover,
.versionsTabView li > *:focus {
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=100)";
filter: alpha(opacity=100);
opacity: 1;
}
#found_versions img {
.versionsTabView img {
cursor: pointer;
padding-right: 4px;
}
#found_versions img.preview {
.versionsTabView img.preview {
cursor: default;
opacity: 1;
}
#found_versions .versionDate {
.versionsTabView .versionDate {
min-width: 100px;
vertical-align: text-bottom;
}
#found_versions .revertVersion {
.versionsTabView .revertVersion {
cursor: pointer;
float: right;
max-width: 130px;

View File

@ -0,0 +1,34 @@
/*
* Copyright (c) 2015
*
* This file is licensed under the Affero General Public License version 3
* or later.
*
* See the COPYING-README file.
*
*/
(function() {
OCA.Versions = OCA.Versions || {};
/**
* @namespace
*/
OCA.Versions.Util = {
/**
* Initialize the versions plugin.
*
* @param {OCA.Files.FileList} fileList file list to be extended
*/
attach: function(fileList) {
if (fileList.id === 'trashbin' || fileList.id === 'files.public') {
return;
}
fileList.registerTabView(new OCA.Versions.VersionsTabView('versionsTabView'));
}
};
})();
OC.Plugins.register('OCA.Files.FileList', OCA.Versions.Util);

View File

@ -0,0 +1,91 @@
/*
* Copyright (c) 2015
*
* This file is licensed under the Affero General Public License version 3
* or later.
*
* See the COPYING-README file.
*
*/
(function() {
/**
* @memberof OCA.Versions
*/
var VersionCollection = OC.Backbone.Collection.extend({
model: OCA.Versions.VersionModel,
/**
* @var OCA.Files.FileInfoModel
*/
_fileInfo: null,
_endReached: false,
_currentIndex: 0,
url: function() {
var url = OC.generateUrl('/apps/files_versions/ajax/getVersions.php');
var query = {
source: this._fileInfo.getFullPath(),
start: this._currentIndex
};
return url + '?' + OC.buildQueryString(query);
},
setFileInfo: function(fileInfo) {
this._fileInfo = fileInfo;
// reset
this._endReached = false;
this._currentIndex = 0;
},
getFileInfo: function() {
return this._fileInfo;
},
hasMoreResults: function() {
return !this._endReached;
},
fetch: function(options) {
if (!options || options.remove) {
this._currentIndex = 0;
}
return OC.Backbone.Collection.prototype.fetch.apply(this, arguments);
},
/**
* Fetch the next set of results
*/
fetchNext: function() {
if (!this.hasMoreResults()) {
return null;
}
if (this._currentIndex === 0) {
return this.fetch();
}
return this.fetch({remove: false});
},
parse: function(result) {
var results = _.map(result.data.versions, function(version) {
var revision = parseInt(version.version, 10);
return {
id: revision,
name: version.name,
fullPath: version.path,
timestamp: revision,
size: version.size
};
});
this._endReached = result.data.endReached;
this._currentIndex += results.length;
return results;
}
});
OCA.Versions = OCA.Versions || {};
OCA.Versions.VersionCollection = VersionCollection;
})();

View File

@ -0,0 +1,77 @@
/*
* Copyright (c) 2015
*
* This file is licensed under the Affero General Public License version 3
* or later.
*
* See the COPYING-README file.
*
*/
(function() {
/**
* @memberof OCA.Versions
*/
var VersionModel = OC.Backbone.Model.extend({
/**
* Restores the original file to this revision
*/
revert: function(options) {
options = options ? _.clone(options) : {};
var model = this;
var file = this.getFullPath();
var revision = this.get('timestamp');
$.ajax({
type: 'GET',
url: OC.generateUrl('/apps/files_versions/ajax/rollbackVersion.php'),
dataType: 'json',
data: {
file: file,
revision: revision
},
success: function(response) {
if (response.status === 'error') {
if (options.error) {
options.error.call(options.context, model, response, options);
}
model.trigger('error', model, response, options);
} else {
if (options.success) {
options.success.call(options.context, model, response, options);
}
model.trigger('revert', model, response, options);
}
}
});
},
getFullPath: function() {
return this.get('fullPath');
},
getPreviewUrl: function() {
var url = OC.generateUrl('/apps/files_versions/preview');
var params = {
file: this.get('fullPath'),
version: this.get('timestamp')
};
return url + '?' + OC.buildQueryString(params);
},
getDownloadUrl: function() {
var url = OC.generateUrl('/apps/files_versions/download.php');
var params = {
file: this.get('fullPath'),
revision: this.get('timestamp')
};
return url + '?' + OC.buildQueryString(params);
}
});
OCA.Versions = OCA.Versions || {};
OCA.Versions.VersionModel = VersionModel;
})();

View File

@ -1,193 +0,0 @@
/*
* Copyright (c) 2014
*
* This file is licensed under the Affero General Public License version 3
* or later.
*
* See the COPYING-README file.
*
*/
/* global scanFiles, escapeHTML, formatDate */
$(document).ready(function(){
// TODO: namespace all this as OCA.FileVersions
if ($('#isPublic').val()){
// no versions actions in public mode
// beware of https://github.com/owncloud/core/issues/4545
// as enabling this might hang Chrome
return;
}
if (OCA.Files) {
// Add versions button to 'files/index.php'
OCA.Files.fileActions.register(
'file',
'Versions',
OC.PERMISSION_UPDATE,
function() {
// Specify icon for hitory button
return OC.imagePath('core','actions/history');
}, function(filename, context){
// Action to perform when clicked
if (scanFiles.scanning){return;}//workaround to prevent additional http request block scanning feedback
var file = context.dir.replace(/(?!<=\/)$|\/$/, '/' + filename);
var createDropDown = true;
// Check if drop down is already visible for a different file
if (($('#dropdown').length > 0) ) {
if ( $('#dropdown').hasClass('drop-versions') && file == $('#dropdown').data('file')) {
createDropDown = false;
}
$('#dropdown').slideUp(OC.menuSpeed);
$('#dropdown').remove();
$('tr').removeClass('mouseOver');
}
if(createDropDown === true) {
createVersionsDropdown(filename, file, context.fileList);
}
}, t('files_versions', 'Versions')
);
}
$(document).on("click", 'span[class="revertVersion"]', function() {
var revision = $(this).attr('id');
var file = $(this).attr('value');
revertFile(file, revision);
});
});
function revertFile(file, revision) {
$.ajax({
type: 'GET',
url: OC.linkTo('files_versions', 'ajax/rollbackVersion.php'),
dataType: 'json',
data: {file: file, revision: revision},
async: false,
success: function(response) {
if (response.status === 'error') {
OC.Notification.show( t('files_version', 'Failed to revert {file} to revision {timestamp}.', {file:file, timestamp:formatDate(revision * 1000)}) );
} else {
$('#dropdown').slideUp(OC.menuSpeed, function() {
$('#dropdown').closest('tr').find('.modified:first').html(relative_modified_date(revision));
$('#dropdown').remove();
$('tr').removeClass('mouseOver');
});
}
}
});
}
function goToVersionPage(url){
window.location.assign(url);
}
function createVersionsDropdown(filename, files, fileList) {
var start = 0;
var fileEl;
var html = '<div id="dropdown" class="drop drop-versions" data-file="'+escapeHTML(files)+'">';
html += '<div id="private">';
html += '<ul id="found_versions">';
html += '</ul>';
html += '</div>';
html += '<input type="button" value="'+ t('files_versions', 'More versions...') + '" name="show-more-versions" id="show-more-versions" style="display: none;" />';
if (filename) {
fileEl = fileList.findFileEl(filename);
fileEl.addClass('mouseOver');
$(html).appendTo(fileEl.find('td.filename'));
} else {
$(html).appendTo($('thead .share'));
}
getVersions(start);
start = start + 5;
$("#show-more-versions").click(function() {
//get more versions
getVersions(start);
start = start + 5;
});
function getVersions(start) {
$.ajax({
type: 'GET',
url: OC.filePath('files_versions', 'ajax', 'getVersions.php'),
dataType: 'json',
data: {source: files, start: start},
async: false,
success: function(result) {
var versions = result.data.versions;
if (result.data.endReached === true) {
$("#show-more-versions").css("display", "none");
} else {
$("#show-more-versions").css("display", "block");
}
if (versions) {
$.each(versions, function(index, row) {
addVersion(row);
});
} else {
$('<div style="text-align:center;">'+ t('files_versions', 'No other versions available') + '</div>').appendTo('#dropdown');
}
$('#found_versions').change(function() {
var revision = parseInt($(this).val());
revertFile(files, revision);
});
}
});
}
function addVersion( revision ) {
var title = formatDate(revision.version*1000);
var name ='<span class="versionDate" title="' + title + '">' + revision.humanReadableTimestamp + '</span>';
var path = OC.filePath('files_versions', '', 'download.php');
var preview = '<img class="preview" src="'+revision.preview+'"/>';
var download ='<a href="' + path + "?file=" + encodeURIComponent(files) + '&revision=' + revision.version + '">';
download+='<img';
download+=' src="' + OC.imagePath('core', 'actions/download') + '"';
download+=' name="downloadVersion" />';
download+=name;
download+='</a>';
var revert='<span class="revertVersion"';
revert+=' id="' + revision.version + '">';
revert+='<img';
revert+=' src="' + OC.imagePath('core', 'actions/history') + '"';
revert+=' name="revertVersion"';
revert+='/>'+t('files_versions', 'Restore')+'</span>';
var version=$('<li/>');
version.attr('value', revision.version);
version.html(preview + download + revert);
// add file here for proper name escaping
version.find('span.revertVersion').attr('value', files);
version.appendTo('#found_versions');
}
$('#dropdown').slideDown(1000);
}
$(this).click(
function(event) {
if ($('#dropdown').has(event.target).length === 0 && $('#dropdown').hasClass('drop-versions')) {
$('#dropdown').slideUp(OC.menuSpeed, function() {
$('#dropdown').remove();
$('tr').removeClass('mouseOver');
});
}
}
);

View File

@ -0,0 +1,198 @@
/*
* Copyright (c) 2015
*
* This file is licensed under the Affero General Public License version 3
* or later.
*
* See the COPYING-README file.
*
*/
(function() {
var TEMPLATE_ITEM =
'<li data-revision="{{timestamp}}">' +
'<img class="preview" src="{{previewUrl}}"/>' +
'<a href="{{downloadUrl}}" class="downloadVersion"><img src="{{downloadIconUrl}}" />' +
'<span class="versiondate has-tooltip" title="{{formattedTimestamp}}">{{relativeTimestamp}}</span>' +
'</a>' +
'<a href="#" class="revertVersion"><img src="{{revertIconUrl}}" />{{revertLabel}}</a>' +
'</li>';
var TEMPLATE =
'<ul class="versions"></ul>' +
'<div class="clear-float"></div>' +
'<div class="empty hidden">{{emptyResultLabel}}</div>' +
'<input type="button" class="showMoreVersions hidden" value="{{moreVersionsLabel}}"' +
' name="show-more-versions" id="show-more-versions" />' +
'<div class="loading hidden" style="height: 50px"></div>';
/**
* @memberof OCA.Versions
*/
var VersionsTabView = OCA.Files.DetailTabView.extend(
/** @lends OCA.Versions.VersionsTabView.prototype */ {
id: 'versionsTabView',
className: 'tab versionsTabView',
_template: null,
$versionsContainer: null,
events: {
'click .revertVersion': '_onClickRevertVersion',
'click .showMoreVersions': '_onClickShowMoreVersions'
},
initialize: function() {
this.collection = new OCA.Versions.VersionCollection();
this.collection.on('request', this._onRequest, this);
this.collection.on('sync', this._onEndRequest, this);
this.collection.on('update', this._onUpdate, this);
this.collection.on('error', this._onError, this);
this.collection.on('add', this._onAddModel, this);
},
getLabel: function() {
return t('files_versions', 'Versions');
},
nextPage: function() {
if (this._loading || !this.collection.hasMoreResults()) {
return;
}
if (this.collection.getFileInfo() && this.collection.getFileInfo().isDirectory()) {
return;
}
this.collection.fetchNext();
},
_onClickShowMoreVersions: function(ev) {
ev.preventDefault();
this.nextPage();
},
_onClickRevertVersion: function(ev) {
var self = this;
var $target = $(ev.target);
var fileInfoModel = this.collection.getFileInfo();
var revision;
if (!$target.is('li')) {
$target = $target.closest('li');
}
ev.preventDefault();
revision = $target.attr('data-revision');
var versionModel = this.collection.get(revision);
versionModel.revert({
success: function() {
// reset and re-fetch the updated collection
self.collection.setFileInfo(fileInfoModel);
self.collection.fetch();
// update original model
fileInfoModel.trigger('busy', fileInfoModel, false);
fileInfoModel.set({
size: versionModel.get('size'),
mtime: versionModel.get('timestamp') * 1000,
// temp dummy, until we can do a PROPFIND
etag: versionModel.get('id') + versionModel.get('timestamp')
});
},
error: function() {
OC.Notification.showTemporary(
t('files_version', 'Failed to revert {file} to revision {timestamp}.', {
file: versionModel.getFullPath(),
timestamp: OC.Util.formatDate(versionModel.get('timestamp') * 1000)
})
);
}
});
// spinner
this._toggleLoading(true);
fileInfoModel.trigger('busy', fileInfoModel, true);
},
_toggleLoading: function(state) {
this._loading = state;
this.$el.find('.loading').toggleClass('hidden', !state);
},
_onRequest: function() {
this._toggleLoading(true);
this.$el.find('.showMoreVersions').addClass('hidden');
},
_onEndRequest: function() {
this._toggleLoading(false);
this.$el.find('.empty').toggleClass('hidden', !!this.collection.length);
this.$el.find('.showMoreVersions').toggleClass('hidden', !this.collection.hasMoreResults());
},
_onAddModel: function(model) {
this.$versionsContainer.append(this.itemTemplate(this._formatItem(model)));
},
template: function(data) {
if (!this._template) {
this._template = Handlebars.compile(TEMPLATE);
}
return this._template(data);
},
itemTemplate: function(data) {
if (!this._itemTemplate) {
this._itemTemplate = Handlebars.compile(TEMPLATE_ITEM);
}
return this._itemTemplate(data);
},
setFileInfo: function(fileInfo) {
if (fileInfo) {
this.render();
this.collection.setFileInfo(fileInfo);
this.collection.reset({silent: true});
this.nextPage();
} else {
this.render();
this.collection.reset();
}
},
_formatItem: function(version) {
var timestamp = version.get('timestamp') * 1000;
return _.extend({
formattedTimestamp: OC.Util.formatDate(timestamp),
relativeTimestamp: OC.Util.relativeModifiedDate(timestamp),
downloadUrl: version.getDownloadUrl(),
downloadIconUrl: OC.imagePath('core', 'actions/download'),
revertIconUrl: OC.imagePath('core', 'actions/history'),
previewUrl: version.getPreviewUrl(),
revertLabel: t('files_versions', 'Restore'),
}, version.attributes);
},
/**
* Renders this details view
*/
render: function() {
this.$el.html(this.template({
emptyResultLabel: t('files_versions', 'No other versions available'),
moreVersionsLabel: t('files_versions', 'More versions...')
}));
this.$el.find('.has-tooltip').tooltip();
this.$versionsContainer = this.$el.find('ul.versions');
this.delegateEvents();
}
});
OCA.Versions = OCA.Versions || {};
OCA.Versions.VersionsTabView = VersionsTabView;
})();

View File

@ -43,6 +43,9 @@ class Hooks {
\OCP\Util::connectHook('OC_Filesystem', 'post_copy', 'OCA\Files_Versions\Hooks', 'copy_hook');
\OCP\Util::connectHook('OC_Filesystem', 'rename', 'OCA\Files_Versions\Hooks', 'pre_renameOrCopy_hook');
\OCP\Util::connectHook('OC_Filesystem', 'copy', 'OCA\Files_Versions\Hooks', 'pre_renameOrCopy_hook');
$eventDispatcher = \OC::$server->getEventDispatcher();
$eventDispatcher->addListener('OCA\Files::loadAdditionalScripts', ['OCA\Files_Versions\Hooks', 'onLoadFilesAppScripts']);
}
/**
@ -154,4 +157,13 @@ class Hooks {
}
}
/**
* Load additional scripts when the files app is visible
*/
public static function onLoadFilesAppScripts() {
\OCP\Util::addScript('files_versions', 'versionmodel');
\OCP\Util::addScript('files_versions', 'versioncollection');
\OCP\Util::addScript('files_versions', 'versionstabview');
\OCP\Util::addScript('files_versions', 'filesplugin');
}
}

View File

@ -0,0 +1,161 @@
/*
* Copyright (c) 2015
*
* This file is licensed under the Affero General Public License version 3
* or later.
*
* See the COPYING-README file.
*
*/
describe('OCA.Versions.VersionCollection', function() {
var VersionCollection = OCA.Versions.VersionCollection;
var collection, fileInfoModel;
beforeEach(function() {
fileInfoModel = new OCA.Files.FileInfoModel({
path: '/subdir',
name: 'some file.txt'
});
collection = new VersionCollection();
collection.setFileInfo(fileInfoModel);
});
it('fetches the next page', function() {
collection.fetchNext();
expect(fakeServer.requests.length).toEqual(1);
expect(fakeServer.requests[0].url).toEqual(
OC.generateUrl('apps/files_versions/ajax/getVersions.php') +
'?source=%2Fsubdir%2Fsome%20file.txt&start=0'
);
fakeServer.requests[0].respond(
200,
{ 'Content-Type': 'application/json' },
JSON.stringify({
status: 'success',
data: {
endReached: false,
versions: [{
version: 10000000,
size: 123,
name: 'some file.txt',
fullPath: '/subdir/some file.txt'
},{
version: 15000000,
size: 150,
name: 'some file.txt',
path: '/subdir/some file.txt'
}]
}
})
);
expect(collection.length).toEqual(2);
expect(collection.hasMoreResults()).toEqual(true);
collection.fetchNext();
expect(fakeServer.requests.length).toEqual(2);
expect(fakeServer.requests[1].url).toEqual(
OC.generateUrl('apps/files_versions/ajax/getVersions.php') +
'?source=%2Fsubdir%2Fsome%20file.txt&start=2'
);
fakeServer.requests[1].respond(
200,
{ 'Content-Type': 'application/json' },
JSON.stringify({
status: 'success',
data: {
endReached: true,
versions: [{
version: 18000000,
size: 123,
name: 'some file.txt',
path: '/subdir/some file.txt'
}]
}
})
);
expect(collection.length).toEqual(3);
expect(collection.hasMoreResults()).toEqual(false);
collection.fetchNext();
// no further requests
expect(fakeServer.requests.length).toEqual(2);
});
it('properly parses the results', function() {
collection.fetchNext();
expect(fakeServer.requests.length).toEqual(1);
expect(fakeServer.requests[0].url).toEqual(
OC.generateUrl('apps/files_versions/ajax/getVersions.php') +
'?source=%2Fsubdir%2Fsome%20file.txt&start=0'
);
fakeServer.requests[0].respond(
200,
{ 'Content-Type': 'application/json' },
JSON.stringify({
status: 'success',
data: {
endReached: false,
versions: [{
version: 10000000,
size: 123,
name: 'some file.txt',
path: '/subdir/some file.txt'
},{
version: 15000000,
size: 150,
name: 'some file.txt',
path: '/subdir/some file.txt'
}]
}
})
);
expect(collection.length).toEqual(2);
var model = collection.at(0);
expect(model.get('id')).toEqual(10000000);
expect(model.get('timestamp')).toEqual(10000000);
expect(model.get('name')).toEqual('some file.txt');
expect(model.get('fullPath')).toEqual('/subdir/some file.txt');
expect(model.get('size')).toEqual(123);
model = collection.at(1);
expect(model.get('id')).toEqual(15000000);
expect(model.get('timestamp')).toEqual(15000000);
expect(model.get('name')).toEqual('some file.txt');
expect(model.get('fullPath')).toEqual('/subdir/some file.txt');
expect(model.get('size')).toEqual(150);
});
it('resets page counted when setting a new file info model', function() {
collection.fetchNext();
expect(fakeServer.requests.length).toEqual(1);
fakeServer.requests[0].respond(
200,
{ 'Content-Type': 'application/json' },
JSON.stringify({
status: 'success',
data: {
endReached: true,
versions: [{
version: 18000000,
size: 123,
name: 'some file.txt',
path: '/subdir/some file.txt'
}]
}
})
);
expect(collection.hasMoreResults()).toEqual(false);
collection.setFileInfo(fileInfoModel);
expect(collection.hasMoreResults()).toEqual(true);
});
});

View File

@ -0,0 +1,96 @@
/*
* Copyright (c) 2015
*
* This file is licensed under the Affero General Public License version 3
* or later.
*
* See the COPYING-README file.
*
*/
describe('OCA.Versions.VersionModel', function() {
var VersionModel = OCA.Versions.VersionModel;
var model;
beforeEach(function() {
model = new VersionModel({
id: 10000000,
timestamp: 10000000,
fullPath: '/subdir/some file.txt',
name: 'some file.txt',
size: 150
});
});
it('returns the full path', function() {
expect(model.getFullPath()).toEqual('/subdir/some file.txt');
});
it('returns the preview url', function() {
expect(model.getPreviewUrl())
.toEqual(OC.generateUrl('/apps/files_versions/preview') +
'?file=%2Fsubdir%2Fsome%20file.txt&version=10000000'
);
});
it('returns the download url', function() {
expect(model.getDownloadUrl())
.toEqual(OC.generateUrl('/apps/files_versions/download.php') +
'?file=%2Fsubdir%2Fsome%20file.txt&revision=10000000'
);
});
describe('reverting', function() {
var revertEventStub;
var successStub;
var errorStub;
beforeEach(function() {
revertEventStub = sinon.stub();
errorStub = sinon.stub();
successStub = sinon.stub();
model.on('revert', revertEventStub);
model.on('error', errorStub);
});
it('tells the server to revert when calling the revert method', function() {
model.revert({
success: successStub
});
expect(fakeServer.requests.length).toEqual(1);
expect(fakeServer.requests[0].url)
.toEqual(
OC.generateUrl('/apps/files_versions/ajax/rollbackVersion.php') +
'?file=%2Fsubdir%2Fsome+file.txt&revision=10000000'
);
fakeServer.requests[0].respond(
200,
{ 'Content-Type': 'application/json' },
JSON.stringify({
status: 'success',
})
);
expect(revertEventStub.calledOnce).toEqual(true);
expect(successStub.calledOnce).toEqual(true);
expect(errorStub.notCalled).toEqual(true);
});
it('triggers error event when server returns a failure', function() {
model.revert({
success: successStub
});
expect(fakeServer.requests.length).toEqual(1);
fakeServer.requests[0].respond(
200,
{ 'Content-Type': 'application/json' },
JSON.stringify({
status: 'error',
})
);
expect(revertEventStub.notCalled).toEqual(true);
expect(successStub.notCalled).toEqual(true);
expect(errorStub.calledOnce).toEqual(true);
});
});
});

View File

@ -0,0 +1,208 @@
/*
* Copyright (c) 2015
*
* This file is licensed under the Affero General Public License version 3
* or later.
*
* See the COPYING-README file.
*
*/
describe('OCA.Versions.VersionsTabView', function() {
var VersionCollection = OCA.Versions.VersionCollection;
var VersionModel = OCA.Versions.VersionModel;
var VersionsTabView = OCA.Versions.VersionsTabView;
var fetchStub, fileInfoModel, tabView, testVersions, clock;
beforeEach(function() {
clock = sinon.useFakeTimers(Date.UTC(2015, 6, 17, 1, 2, 0, 3));
var time1 = Date.UTC(2015, 6, 17, 1, 2, 0, 3) / 1000;
var time2 = Date.UTC(2015, 6, 15, 1, 2, 0, 3) / 1000;
var version1 = new VersionModel({
id: time1,
timestamp: time1,
name: 'some file.txt',
size: 140,
fullPath: '/subdir/some file.txt'
});
var version2 = new VersionModel({
id: time2,
timestamp: time2,
name: 'some file.txt',
size: 150,
fullPath: '/subdir/some file.txt'
});
testVersions = [version1, version2];
fetchStub = sinon.stub(VersionCollection.prototype, 'fetch');
fileInfoModel = new OCA.Files.FileInfoModel({
id: 123,
name: 'test.txt'
});
tabView = new VersionsTabView();
tabView.render();
});
afterEach(function() {
fetchStub.restore();
tabView.remove();
clock.restore();
});
describe('rendering', function() {
it('reloads matching versions when setting file info model', function() {
tabView.setFileInfo(fileInfoModel);
expect(fetchStub.calledOnce).toEqual(true);
});
it('renders loading icon while fetching versions', function() {
tabView.setFileInfo(fileInfoModel);
tabView.collection.trigger('request');
expect(tabView.$el.find('.loading').length).toEqual(1);
expect(tabView.$el.find('.versions li').length).toEqual(0);
});
it('renders versions', function() {
tabView.setFileInfo(fileInfoModel);
tabView.collection.set(testVersions);
var version1 = testVersions[0];
var version2 = testVersions[1];
var $versions = tabView.$el.find('.versions>li');
expect($versions.length).toEqual(2);
var $item = $versions.eq(0);
expect($item.find('.downloadVersion').attr('href')).toEqual(version1.getDownloadUrl());
expect($item.find('.versiondate').text()).toEqual('a few seconds ago');
expect($item.find('.revertVersion').length).toEqual(1);
expect($item.find('.preview').attr('src')).toEqual(version1.getPreviewUrl());
$item = $versions.eq(1);
expect($item.find('.downloadVersion').attr('href')).toEqual(version2.getDownloadUrl());
expect($item.find('.versiondate').text()).toEqual('2 days ago');
expect($item.find('.revertVersion').length).toEqual(1);
expect($item.find('.preview').attr('src')).toEqual(version2.getPreviewUrl());
});
});
describe('More versions', function() {
var hasMoreResultsStub;
beforeEach(function() {
tabView.collection.set(testVersions);
hasMoreResultsStub = sinon.stub(VersionCollection.prototype, 'hasMoreResults');
});
afterEach(function() {
hasMoreResultsStub.restore();
});
it('shows "More versions" button when more versions are available', function() {
hasMoreResultsStub.returns(true);
tabView.collection.trigger('sync');
expect(tabView.$el.find('.showMoreVersions').hasClass('hidden')).toEqual(false);
});
it('does not show "More versions" button when more versions are available', function() {
hasMoreResultsStub.returns(false);
tabView.collection.trigger('sync');
expect(tabView.$el.find('.showMoreVersions').hasClass('hidden')).toEqual(true);
});
it('fetches and appends the next page when clicking the "More" button', function() {
hasMoreResultsStub.returns(true);
expect(fetchStub.notCalled).toEqual(true);
tabView.$el.find('.showMoreVersions').click();
expect(fetchStub.calledOnce).toEqual(true);
});
it('appends version to the list when added to collection', function() {
var time3 = Date.UTC(2015, 6, 10, 1, 0, 0, 0) / 1000;
var version3 = new VersionModel({
id: time3,
timestamp: time3,
name: 'some file.txt',
size: 54,
fullPath: '/subdir/some file.txt'
});
tabView.collection.add(version3);
expect(tabView.$el.find('.versions>li').length).toEqual(3);
var $item = tabView.$el.find('.versions>li').eq(2);
expect($item.find('.downloadVersion').attr('href')).toEqual(version3.getDownloadUrl());
expect($item.find('.versiondate').text()).toEqual('7 days ago');
expect($item.find('.revertVersion').length).toEqual(1);
expect($item.find('.preview').attr('src')).toEqual(version3.getPreviewUrl());
});
});
describe('Reverting', function() {
var revertStub;
beforeEach(function() {
revertStub = sinon.stub(VersionModel.prototype, 'revert');
tabView.setFileInfo(fileInfoModel);
tabView.collection.set(testVersions);
});
afterEach(function() {
revertStub.restore();
});
it('tells the model to revert when clicking "Revert"', function() {
tabView.$el.find('.revertVersion').eq(1).click();
expect(revertStub.calledOnce).toEqual(true);
});
it('triggers busy state during revert', function() {
var busyStub = sinon.stub();
fileInfoModel.on('busy', busyStub);
tabView.$el.find('.revertVersion').eq(1).click();
expect(busyStub.calledOnce).toEqual(true);
expect(busyStub.calledWith(fileInfoModel, true)).toEqual(true);
busyStub.reset();
revertStub.getCall(0).args[0].success();
expect(busyStub.calledOnce).toEqual(true);
expect(busyStub.calledWith(fileInfoModel, false)).toEqual(true);
});
it('updates the file info model with the information from the reverted revision', function() {
var changeStub = sinon.stub();
fileInfoModel.on('change', changeStub);
tabView.$el.find('.revertVersion').eq(1).click();
expect(changeStub.notCalled).toEqual(true);
revertStub.getCall(0).args[0].success();
expect(changeStub.calledOnce).toEqual(true);
var changes = changeStub.getCall(0).args[0].changed;
expect(changes.size).toEqual(150);
expect(changes.mtime).toEqual(testVersions[1].get('timestamp') * 1000);
expect(changes.etag).toBeDefined();
});
it('shows notification on revert error', function() {
var notificationStub = sinon.stub(OC.Notification, 'showTemporary');
tabView.$el.find('.revertVersion').eq(1).click();
revertStub.getCall(0).args[0].error();
expect(notificationStub.calledOnce).toEqual(true);
notificationStub.restore();
});
});
});

View File

@ -70,6 +70,16 @@ module.exports = function(config) {
],
testFiles: ['apps/files_external/tests/js/*.js']
},
{
name: 'files_versions',
srcFiles: [
// need to enforce loading order...
'apps/files_versions/js/versionmodel.js',
'apps/files_versions/js/versioncollection.js',
'apps/files_versions/js/versionstabview.js'
],
testFiles: ['apps/files_versions/tests/js/**/*.js']
},
{
name: 'settings',
srcFiles: [