From 41061dd1b08c75d1ce4115dd6e080e4552e96a78 Mon Sep 17 00:00:00 2001 From: fenn-cs Date: Fri, 8 Dec 2023 18:02:14 +0100 Subject: [PATCH] Allow user to finish typing date before formatting Debounce `onExpirationChange` to avoid calling `formatDateToString` on invalid on uncompletely inputed date strings. Signed-off-by: fenn-cs --- apps/files_sharing/src/mixins/SharesMixin.js | 5 +- apps/files_trashbin/tests/js/appSpec.js | 70 +++ apps/files_trashbin/tests/js/filelistSpec.js | 397 ++++++++++++++++++ .../tests/js/systemtagsinfoviewSpec.js | 252 +++++++++++ apps/user_status/src/views/Dashboard.vue | 121 ++++++ 5 files changed, 842 insertions(+), 3 deletions(-) create mode 100644 apps/files_trashbin/tests/js/appSpec.js create mode 100644 apps/files_trashbin/tests/js/filelistSpec.js create mode 100644 apps/systemtags/tests/js/systemtagsinfoviewSpec.js create mode 100644 apps/user_status/src/views/Dashboard.vue diff --git a/apps/files_sharing/src/mixins/SharesMixin.js b/apps/files_sharing/src/mixins/SharesMixin.js index 41bdf302f7a8c..b76b72cebe9ee 100644 --- a/apps/files_sharing/src/mixins/SharesMixin.js +++ b/apps/files_sharing/src/mixins/SharesMixin.js @@ -221,10 +221,9 @@ export default { * * @param {Date} date */ - onExpirationChange(date) { + onExpirationChange: debounce((date) => { this.share.expireDate = this.formatDateToString(new Date(date)) - }, - + }, 500), /** * Uncheck expire date * We need this method because @update:checked diff --git a/apps/files_trashbin/tests/js/appSpec.js b/apps/files_trashbin/tests/js/appSpec.js new file mode 100644 index 0000000000000..281e7bbc2ba4a --- /dev/null +++ b/apps/files_trashbin/tests/js/appSpec.js @@ -0,0 +1,70 @@ +/** +* @copyright 2014 Vincent Petry + * + * @author Vincent Petry + * + * @license AGPL-3.0-or-later + * + * 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 . + * + */ + +describe('OCA.Trashbin.App tests', function() { + var App = OCA.Trashbin.App; + + beforeEach(function() { + $('#testArea').append( + '
' + + '
' + + '
' + + '' + + '' + + '
' + + '' + ); + App.initialize($('#app-content-trashbin')); + }); + afterEach(function() { + App._initialized = false; + App.fileList = null; + }); + + describe('initialization', function() { + it('creates a custom filelist instance', function() { + App.initialize(); + expect(App.fileList).toBeDefined(); + expect(App.fileList.$el.is('#app-content-trashbin')).toEqual(true); + }); + + it('registers custom file actions', function() { + var fileActions; + App.initialize(); + + fileActions = App.fileList.fileActions; + + expect(fileActions.actions.all).toBeDefined(); + expect(fileActions.actions.all.Restore).toBeDefined(); + expect(fileActions.actions.all.Delete).toBeDefined(); + + expect(fileActions.actions.all.Rename).not.toBeDefined(); + expect(fileActions.actions.all.Download).not.toBeDefined(); + + expect(fileActions.defaults.dir).toEqual('Open'); + }); + }); +}); diff --git a/apps/files_trashbin/tests/js/filelistSpec.js b/apps/files_trashbin/tests/js/filelistSpec.js new file mode 100644 index 0000000000000..9e27188efb8b6 --- /dev/null +++ b/apps/files_trashbin/tests/js/filelistSpec.js @@ -0,0 +1,397 @@ +/** + * @copyright 2014 Vincent Petry + * + * @author Abijeet + * @author Christoph Wurst + * @author Jan C. Borchardt + * @author Jan-Christoph Borchardt + * @author John Molakvoæ + * @author Robin Appelman + * @author Vincent Petry + * + * @license AGPL-3.0-or-later + * + * 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 . + * + */ + +describe('OCA.Trashbin.FileList tests', function () { + var testFiles, alertStub, notificationStub, fileList, client; + + beforeEach(function () { + alertStub = sinon.stub(OC.dialogs, 'alert'); + notificationStub = sinon.stub(OC.Notification, 'show'); + + client = new OC.Files.Client({ + host: 'localhost', + port: 80, + root: '/remote.php/dav/trashbin/user', + useHTTPS: OC.getProtocol() === 'https' + }); + + // init parameters and test table elements + $('#testArea').append( + '
' + + // set this but it shouldn't be used (could be the one from the + // files app) + '' + + // dummy controls + '
' + + '
' + + '
' + + '
' + + // dummy table + // TODO: at some point this will be rendered by the fileList class itself! + '' + + '' + + '' + + '' + + '
' + + '
Empty content message
' + + '
' + ); + + testFiles = [{ + id: 1, + type: 'file', + name: 'One.txt.d11111', + displayName: 'One.txt', + mtime: 11111000, + mimetype: 'text/plain', + etag: 'abc' + }, { + id: 2, + type: 'file', + name: 'Two.jpg.d22222', + displayName: 'Two.jpg', + mtime: 22222000, + mimetype: 'image/jpeg', + etag: 'def', + }, { + id: 3, + type: 'file', + name: 'Three.pdf.d33333', + displayName: 'Three.pdf', + mtime: 33333000, + mimetype: 'application/pdf', + etag: '123', + }, { + id: 4, + type: 'dir', + mtime: 99999000, + name: 'somedir.d99999', + displayName: 'somedir', + mimetype: 'httpd/unix-directory', + etag: '456' + }]; + + // register file actions like the trashbin App does + var fileActions = OCA.Trashbin.App._createFileActions(fileList); + fileList = new OCA.Trashbin.FileList( + $('#app-content'), { + fileActions: fileActions, + multiSelectMenu: [{ + name: 'restore', + displayName: t('files', 'Restore'), + iconClass: 'icon-history', + }, + { + name: 'delete', + displayName: t('files', 'Delete'), + iconClass: 'icon-delete', + } + ], + client: client + } + ); + }); + afterEach(function () { + testFiles = undefined; + fileList.destroy(); + fileList = undefined; + + notificationStub.restore(); + alertStub.restore(); + }); + describe('Initialization', function () { + it('Sorts by mtime by default', function () { + expect(fileList._sort).toEqual('mtime'); + expect(fileList._sortDirection).toEqual('desc'); + }); + it('Always returns read and delete permission', function () { + expect(fileList.getDirectoryPermissions()).toEqual(OC.PERMISSION_READ | OC.PERMISSION_DELETE); + }); + }); + describe('Breadcrumbs', function () { + beforeEach(function () { + var data = { + status: 'success', + data: { + files: testFiles, + permissions: 1 + } + }; + fakeServer.respondWith(/\/index\.php\/apps\/files_trashbin\/ajax\/list.php\?dir=%2Fsubdir/, [ + 200, { + "Content-Type": "application/json" + }, + JSON.stringify(data) + ]); + }); + it('links the breadcrumb to the trashbin view', function () { + fileList.changeDirectory('/subdir', false, true); + fakeServer.respond(); + var $crumbs = fileList.$el.find('.files-controls .crumb'); + expect($crumbs.length).toEqual(3); + expect($crumbs.eq(1).find('a').text()).toEqual('Home'); + expect($crumbs.eq(1).find('a').attr('href')) + .toEqual(OC.getRootPath() + '/index.php/apps/files?view=trashbin&dir=/'); + expect($crumbs.eq(2).find('a').text()).toEqual('subdir'); + expect($crumbs.eq(2).find('a').attr('href')) + .toEqual(OC.getRootPath() + '/index.php/apps/files?view=trashbin&dir=/subdir'); + }); + }); + describe('Rendering rows', function () { + it('renders rows with the correct data when in root', function () { + // dir listing is false when in root + fileList.setFiles(testFiles); + var $rows = fileList.$el.find('tbody tr'); + var $tr = $rows.eq(0); + expect($rows.length).toEqual(4); + expect($tr.attr('data-id')).toEqual('1'); + expect($tr.attr('data-type')).toEqual('file'); + expect($tr.attr('data-file')).toEqual('One.txt.d11111'); + expect($tr.attr('data-size')).not.toBeDefined(); + expect($tr.attr('data-etag')).toEqual('abc'); + expect($tr.attr('data-permissions')).toEqual('9'); // read and delete + expect($tr.attr('data-mime')).toEqual('text/plain'); + expect($tr.attr('data-mtime')).toEqual('11111000'); + expect($tr.find('a.name').attr('href')).toEqual('#'); + + expect($tr.find('.nametext').text().trim()).toEqual('One.txt'); + + expect(fileList.findFileEl('One.txt.d11111')[0]).toEqual($tr[0]); + }); + it('renders rows with the correct data when in root after calling setFiles with the same data set', function () { + // dir listing is false when in root + fileList.setFiles(testFiles); + fileList.setFiles(fileList.files); + var $rows = fileList.$el.find('tbody tr'); + var $tr = $rows.eq(0); + expect($rows.length).toEqual(4); + expect($tr.attr('data-id')).toEqual('1'); + expect($tr.attr('data-type')).toEqual('file'); + expect($tr.attr('data-file')).toEqual('One.txt.d11111'); + expect($tr.attr('data-size')).not.toBeDefined(); + expect($tr.attr('data-etag')).toEqual('abc'); + expect($tr.attr('data-permissions')).toEqual('9'); // read and delete + expect($tr.attr('data-mime')).toEqual('text/plain'); + expect($tr.attr('data-mtime')).toEqual('11111000'); + expect($tr.find('a.name').attr('href')).toEqual('#'); + + expect($tr.find('.nametext').text().trim()).toEqual('One.txt'); + + expect(fileList.findFileEl('One.txt.d11111')[0]).toEqual($tr[0]); + }); + it('renders rows with the correct data when in subdirectory', function () { + fileList.setFiles(testFiles.map(function (file) { + file.name = file.displayName; + return file; + })); + var $rows = fileList.$el.find('tbody tr'); + var $tr = $rows.eq(0); + expect($rows.length).toEqual(4); + expect($tr.attr('data-id')).toEqual('1'); + expect($tr.attr('data-type')).toEqual('file'); + expect($tr.attr('data-file')).toEqual('One.txt'); + expect($tr.attr('data-size')).not.toBeDefined(); + expect($tr.attr('data-etag')).toEqual('abc'); + expect($tr.attr('data-permissions')).toEqual('9'); // read and delete + expect($tr.attr('data-mime')).toEqual('text/plain'); + expect($tr.attr('data-mtime')).toEqual('11111000'); + expect($tr.find('a.name').attr('href')).toEqual('#'); + + expect($tr.find('.nametext').text().trim()).toEqual('One.txt'); + + expect(fileList.findFileEl('One.txt')[0]).toEqual($tr[0]); + }); + it('does not render a size column', function () { + expect(fileList.$el.find('tbody tr .filesize').length).toEqual(0); + }); + }); + describe('File actions', function () { + describe('Deleting single files', function () { + // TODO: checks ajax call + // TODO: checks spinner + // TODO: remove item after delete + // TODO: bring back item if delete failed + }); + describe('Restoring single files', function () { + // TODO: checks ajax call + // TODO: checks spinner + // TODO: remove item after restore + // TODO: bring back item if restore failed + }); + }); + describe('file previews', function () { + // TODO: check that preview URL is going through files_trashbin + }); + describe('loading file list', function () { + // TODO: check that ajax URL is going through files_trashbin + }); + describe('breadcrumbs', function () { + // TODO: test label + URL + }); + describe('elementToFile', function () { + var $tr; + + beforeEach(function () { + fileList.setFiles(testFiles); + $tr = fileList.findFileEl('One.txt.d11111'); + }); + + it('converts data attributes to file info structure', function () { + var fileInfo = fileList.elementToFile($tr); + expect(fileInfo.id).toEqual(1); + expect(fileInfo.name).toEqual('One.txt.d11111'); + expect(fileInfo.displayName).toEqual('One.txt'); + expect(fileInfo.mtime).toEqual(11111000); + expect(fileInfo.etag).toEqual('abc'); + expect(fileInfo.permissions).toEqual(OC.PERMISSION_READ | OC.PERMISSION_DELETE); + expect(fileInfo.mimetype).toEqual('text/plain'); + expect(fileInfo.type).toEqual('file'); + }); + }); + describe('Global Actions', function () { + beforeEach(function () { + fileList.setFiles(testFiles); + fileList.findFileEl('One.txt.d11111').find('input:checkbox').click(); + fileList.findFileEl('Three.pdf.d33333').find('input:checkbox').click(); + fileList.findFileEl('somedir.d99999').find('input:checkbox').click(); + fileList.$el.find('.actions-selected').click(); + }); + + afterEach(function () { + fileList.$el.find('.actions-selected').click(); + }); + + describe('Delete', function () { + it('Shows trashbin actions', function () { + // visible because a few files were selected + expect($('.selectedActions').is(':visible')).toEqual(true); + expect($('.selectedActions .item-delete').is(':visible')).toEqual(true); + expect($('.selectedActions .item-restore').is(':visible')).toEqual(true); + + // check + fileList.$el.find('.select-all').click(); + + // stays visible + expect($('.selectedActions').is(':visible')).toEqual(true); + expect($('.selectedActions .item-delete').is(':visible')).toEqual(true); + expect($('.selectedActions .item-restore').is(':visible')).toEqual(true); + + // uncheck + fileList.$el.find('.select-all').click(); + + // becomes hidden now + expect($('.selectedActions').is(':visible')).toEqual(false); + expect($('.selectedActions .item-delete').is(':visible')).toEqual(false); + expect($('.selectedActions .item-restore').is(':visible')).toEqual(false); + }); + it('Deletes selected files when "Delete" clicked', function (done) { + var request; + var promise = fileList._onClickDeleteSelected({ + preventDefault: function () { + } + }); + var files = ["One.txt.d11111", "Three.pdf.d33333", "somedir.d99999"]; + expect(fakeServer.requests.length).toEqual(files.length); + for (var i = 0; i < files.length; i++) { + request = fakeServer.requests[i]; + expect(request.url).toEqual(OC.getRootPath() + '/remote.php/dav/trashbin/user/trash/' + files[i]); + request.respond(200); + } + return promise.then(function () { + expect(fileList.findFileEl('One.txt.d11111').length).toEqual(0); + expect(fileList.findFileEl('Three.pdf.d33333').length).toEqual(0); + expect(fileList.findFileEl('somedir.d99999').length).toEqual(0); + expect(fileList.findFileEl('Two.jpg.d22222').length).toEqual(1); + }).then(done, done); + }); + it('Deletes all files when all selected when "Delete" clicked', function (done) { + var request; + $('.select-all').click(); + var promise = fileList._onClickDeleteSelected({ + preventDefault: function () { + } + }); + expect(fakeServer.requests.length).toEqual(1); + request = fakeServer.requests[0]; + expect(request.url).toEqual(OC.getRootPath() + '/remote.php/dav/trashbin/user/trash'); + request.respond(200); + return promise.then(function () { + expect(fileList.isEmpty).toEqual(true); + }).then(done, done); + }); + }); + describe('Restore', function () { + it('Restores selected files when "Restore" clicked', function (done) { + var request; + var promise = fileList._onClickRestoreSelected({ + preventDefault: function () { + } + }); + var files = ["One.txt.d11111", "Three.pdf.d33333", "somedir.d99999"]; + expect(fakeServer.requests.length).toEqual(files.length); + for (var i = 0; i < files.length; i++) { + request = fakeServer.requests[i]; + expect(request.url).toEqual(OC.getRootPath() + '/remote.php/dav/trashbin/user/trash/' + files[i]); + expect(request.requestHeaders.Destination).toEqual(OC.getRootPath() + '/remote.php/dav/trashbin/user/restore/' + files[i]); + request.respond(200); + } + return promise.then(function() { + expect(fileList.findFileEl('One.txt.d11111').length).toEqual(0); + expect(fileList.findFileEl('Three.pdf.d33333').length).toEqual(0); + expect(fileList.findFileEl('somedir.d99999').length).toEqual(0); + expect(fileList.findFileEl('Two.jpg.d22222').length).toEqual(1); + }).then(done, done); + }); + it('Restores all files when all selected when "Restore" clicked', function (done) { + var request; + $('.select-all').click(); + var promise = fileList._onClickRestoreSelected({ + preventDefault: function () { + } + }); + var files = ["One.txt.d11111", "Two.jpg.d22222", "Three.pdf.d33333", "somedir.d99999"]; + expect(fakeServer.requests.length).toEqual(files.length); + for (var i = 0; i < files.length; i++) { + request = fakeServer.requests[i]; + expect(request.url).toEqual(OC.getRootPath() + '/remote.php/dav/trashbin/user/trash/' + files[i]); + expect(request.requestHeaders.Destination).toEqual(OC.getRootPath() + '/remote.php/dav/trashbin/user/restore/' + files[i]); + request.respond(200); + } + return promise.then(function() { + expect(fileList.isEmpty).toEqual(true); + }).then(done, done); + }); + }); + }); +}); diff --git a/apps/systemtags/tests/js/systemtagsinfoviewSpec.js b/apps/systemtags/tests/js/systemtagsinfoviewSpec.js new file mode 100644 index 0000000000000..5c4b5bf6bd19a --- /dev/null +++ b/apps/systemtags/tests/js/systemtagsinfoviewSpec.js @@ -0,0 +1,252 @@ +/** +* @copyright 2016 Vincent Petry + * + * @author Daniel Calviño Sánchez + * @author John Molakvoæ + * @author Vincent Petry + * + * @license AGPL-3.0-or-later + * + * 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 . + * + */ + +describe('OCA.SystemTags.SystemTagsInfoView tests', function() { + var isAdminStub; + var view; + var clock; + + beforeEach(function() { + clock = sinon.useFakeTimers(); + view = new OCA.SystemTags.SystemTagsInfoView(); + $('#testArea').append(view.$el); + isAdminStub = sinon.stub(OC, 'isUserAdmin').returns(true); + }); + afterEach(function() { + isAdminStub.restore(); + clock.restore(); + view.remove(); + view = undefined; + }); + describe('rendering', function() { + it('renders input field view', function() { + view.render(); + expect(view.$el.find('input[name=tags]').length).toEqual(1); + }); + it('fetches selected tags then renders when setting file info', function() { + var fetchStub = sinon.stub(OC.SystemTags.SystemTagsMappingCollection.prototype, 'fetch'); + var setDataStub = sinon.stub(OC.SystemTags.SystemTagsInputField.prototype, 'setData'); + + expect(view.$el.hasClass('hidden')).toEqual(false); + + view.setFileInfo({id: '123'}); + expect(view.$el.find('input[name=tags]').length).toEqual(1); + + expect(fetchStub.calledOnce).toEqual(true); + expect(view.selectedTagsCollection.url()) + .toEqual(OC.linkToRemote('dav') + '/systemtags-relations/files/123'); + + view.selectedTagsCollection.add([ + {id: '1', name: 'test1'}, + {id: '3', name: 'test3'} + ]); + + fetchStub.yieldTo('success', view.selectedTagsCollection); + expect(setDataStub.calledOnce).toEqual(true); + expect(setDataStub.getCall(0).args[0]).toEqual([{ + id: '1', name: 'test1', userVisible: true, userAssignable: true, canAssign: true + }, { + id: '3', name: 'test3', userVisible: true, userAssignable: true, canAssign: true + }]); + + expect(view.$el.hasClass('hidden')).toEqual(false); + + fetchStub.restore(); + setDataStub.restore(); + }); + it('overrides initSelection to use the local collection', function() { + var inputViewSpy = sinon.spy(OC.SystemTags, 'SystemTagsInputField'); + var element = $(''); + view.remove(); + view = new OCA.SystemTags.SystemTagsInfoView(); + view.selectedTagsCollection.add([ + {id: '1', name: 'test1'}, + {id: '3', name: 'test3', userVisible: false, userAssignable: false, canAssign: false} + ]); + + var callback = sinon.stub(); + inputViewSpy.getCall(0).args[0].initSelection(element, callback); + + expect(callback.calledOnce).toEqual(true); + expect(callback.getCall(0).args[0]).toEqual([{ + id: '1', name: 'test1', userVisible: true, userAssignable: true, canAssign: true + }, { + id: '3', name: 'test3', userVisible: false, userAssignable: false, canAssign: false + }]); + + inputViewSpy.restore(); + }); + it('sets locked flag on non-assignable tags when user is not an admin', function() { + isAdminStub.returns(false); + + var inputViewSpy = sinon.spy(OC.SystemTags, 'SystemTagsInputField'); + var element = $(''); + view.remove(); + view = new OCA.SystemTags.SystemTagsInfoView(); + view.selectedTagsCollection.add([ + {id: '1', name: 'test1'}, + {id: '3', name: 'test3', userAssignable: false, canAssign: false} + ]); + + var callback = sinon.stub(); + inputViewSpy.getCall(0).args[0].initSelection(element, callback); + + expect(callback.calledOnce).toEqual(true); + expect(callback.getCall(0).args[0]).toEqual([{ + id: '1', name: 'test1', userVisible: true, userAssignable: true, canAssign: true + }, { + id: '3', name: 'test3', userVisible: true, userAssignable: false, canAssign: false, locked: true + }]); + + inputViewSpy.restore(); + }); + it('does not set locked flag on non-assignable tags when canAssign overrides it with true', function() { + isAdminStub.returns(false); + + var inputViewSpy = sinon.spy(OC.SystemTags, 'SystemTagsInputField'); + var element = $(''); + view.remove(); + view = new OCA.SystemTags.SystemTagsInfoView(); + view.selectedTagsCollection.add([ + {id: '1', name: 'test1'}, + {id: '4', name: 'test4', userAssignable: false, canAssign: true} + ]); + + var callback = sinon.stub(); + inputViewSpy.getCall(0).args[0].initSelection(element, callback); + + expect(callback.calledOnce).toEqual(true); + expect(callback.getCall(0).args[0]).toEqual([{ + id: '1', name: 'test1', userVisible: true, userAssignable: true, canAssign: true + }, { + id: '4', name: 'test4', userVisible: true, userAssignable: false, canAssign: true + }]); + + inputViewSpy.restore(); + }); + }); + describe('events', function() { + var allTagsCollection; + beforeEach(function() { + allTagsCollection = view._inputView.collection; + + allTagsCollection.add([ + {id: '1', name: 'test1'}, + {id: '2', name: 'test2'}, + {id: '3', name: 'test3'} + ]); + + view.selectedTagsCollection.add([ + {id: '1', name: 'test1'}, + {id: '3', name: 'test3'} + ]); + view.render(); + }); + + it('renames model in selection collection on rename', function() { + allTagsCollection.get('3').set('name', 'test3_renamed'); + + expect(view.selectedTagsCollection.get('3').get('name')).toEqual('test3_renamed'); + }); + + it('adds tag to selection collection when selected by input', function() { + var createStub = sinon.stub(OC.SystemTags.SystemTagsMappingCollection.prototype, 'create'); + view._inputView.trigger('select', allTagsCollection.get('2')); + + expect(createStub.calledOnce).toEqual(true); + expect(createStub.getCall(0).args[0]).toEqual({ + id: '2', + name: 'test2', + userVisible: true, + userAssignable: true, + canAssign: true + }); + + createStub.restore(); + }); + it('removes tag from selection collection when deselected by input', function() { + var destroyStub = sinon.stub(OC.SystemTags.SystemTagModel.prototype, 'destroy'); + view._inputView.trigger('deselect', '3'); + + expect(destroyStub.calledOnce).toEqual(true); + expect(destroyStub.calledOn(view.selectedTagsCollection.get('3'))).toEqual(true); + + destroyStub.restore(); + }); + + it('removes tag from selection whenever the tag was deleted globally', function() { + expect(view.selectedTagsCollection.get('3')).not.toBeFalsy(); + + allTagsCollection.remove('3'); + + expect(view.selectedTagsCollection.get('3')).toBeFalsy(); + + }); + }); + describe('visibility', function() { + it('reports visibility based on the "hidden" class name', function() { + view.$el.addClass('hidden'); + + expect(view.isVisible()).toBeFalsy(); + + view.$el.removeClass('hidden'); + + expect(view.isVisible()).toBeTruthy(); + }); + it('is visible after rendering', function() { + view.render(); + + expect(view.isVisible()).toBeTruthy(); + }); + it('shows and hides the element', function() { + view.show(); + + expect(view.isVisible()).toBeTruthy(); + + view.hide(); + + expect(view.isVisible()).toBeFalsy(); + + view.show(); + + expect(view.isVisible()).toBeTruthy(); + }); + }); + describe('select2', function() { + var select2Stub; + + beforeEach(function() { + select2Stub = sinon.stub($.fn, 'select2'); + }); + afterEach(function() { + select2Stub.restore(); + }); + it('opens dropdown', function() { + view.openDropdown(); + + expect(select2Stub.calledOnce).toBeTruthy(); + expect(select2Stub.withArgs('open')).toBeTruthy(); + }); + }); +}); diff --git a/apps/user_status/src/views/Dashboard.vue b/apps/user_status/src/views/Dashboard.vue new file mode 100644 index 0000000000000..ef5a9832ebccd --- /dev/null +++ b/apps/user_status/src/views/Dashboard.vue @@ -0,0 +1,121 @@ + + + + + + +