From bf6947abf9a03a5599605e89a9181dfd0fd45445 Mon Sep 17 00:00:00 2001 From: Denys Vuika Date: Tue, 28 Jun 2016 15:10:05 +0100 Subject: [PATCH] Unit tests for document list --- .../src/assets/document-library.model.mock.ts | 53 ++ .../src/components/document-list.spec.ts | 453 ++++++++++++++++-- .../src/components/document-list.ts | 15 +- .../src/models/document-library.model.ts | 53 -- 4 files changed, 487 insertions(+), 87 deletions(-) create mode 100644 ng2-components/ng2-alfresco-documentlist/src/assets/document-library.model.mock.ts diff --git a/ng2-components/ng2-alfresco-documentlist/src/assets/document-library.model.mock.ts b/ng2-components/ng2-alfresco-documentlist/src/assets/document-library.model.mock.ts new file mode 100644 index 0000000000..a167d60b65 --- /dev/null +++ b/ng2-components/ng2-alfresco-documentlist/src/assets/document-library.model.mock.ts @@ -0,0 +1,53 @@ +/*! + * @license + * Copyright 2016 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + NodePaging, + MinimalNodeEntity, + MinimalNodeEntryEntity, + PathInfoEntity, + ContentInfo +} from '../models/document-library.model'; + +export class PageNode extends NodePaging { + constructor() { + super(); + } +} + +export class FileNode extends MinimalNodeEntity { + constructor(name?: string) { + super(); + this.entry = new MinimalNodeEntryEntity(); + this.entry.isFile = true; + this.entry.isFolder = false; + this.entry.name = name; + this.entry.path = new PathInfoEntity(); + this.entry.content = new ContentInfo(); + } +} + +export class FolderNode extends MinimalNodeEntity { + constructor(name?: string) { + super(); + this.entry = new MinimalNodeEntryEntity(); + this.entry.isFile = false; + this.entry.isFolder = true; + this.entry.name = name; + this.entry.path = new PathInfoEntity(); + } +} diff --git a/ng2-components/ng2-alfresco-documentlist/src/components/document-list.spec.ts b/ng2-components/ng2-alfresco-documentlist/src/components/document-list.spec.ts index 67898ecac6..c9d89495d0 100644 --- a/ng2-components/ng2-alfresco-documentlist/src/components/document-list.spec.ts +++ b/ng2-components/ng2-alfresco-documentlist/src/components/document-list.spec.ts @@ -21,28 +21,40 @@ import { expect, beforeEach } from '@angular/core/testing'; - +import { NgZone } from '@angular/core'; import { DocumentList } from './document-list'; import { ContentColumnModel } from '../models/content-column.model'; import { AlfrescoServiceMock } from '../assets/alfresco.service.mock'; -import { MinimalNodeEntity, MinimalNodeEntryEntity } from '../models/document-library.model'; import { ContentActionModel } from '../models/content-action.model'; +import { + PageNode, + FileNode, + FolderNode +} from '../assets/document-library.model.mock'; +import { ColumnSortingModel } from '../models/column-sorting.model'; describe('DocumentList', () => { let alfrescoServiceMock: AlfrescoServiceMock; let documentList: DocumentList; let eventMock: any; + let componentHandler; beforeEach(() => { alfrescoServiceMock = new AlfrescoServiceMock(); - documentList = new DocumentList(alfrescoServiceMock, null); + let zone = new NgZone(false); + documentList = new DocumentList(alfrescoServiceMock, zone); eventMock = { preventDefault: function () { console.log('mock preventDefault'); } }; + + componentHandler = jasmine.createSpyObj('componentHandler', [ + 'upgradeAllRegistered' + ]); + window['componentHandler'] = componentHandler; }); it('should setup default columns', () => { @@ -84,9 +96,7 @@ describe('DocumentList', () => { let url = 'URL'; spyOn(alfrescoServiceMock, 'getDocumentThumbnailUrl').and.returnValue(url); - let node = new MinimalNodeEntity(); - node.entry = new MinimalNodeEntryEntity(); - node.entry.isFile = true; + let node = new FileNode(); documentList.thumbnails = true; let result = documentList.getThumbnailUrl(node); @@ -104,15 +114,8 @@ describe('DocumentList', () => { expect(alfrescoServiceMock.getDocumentThumbnailUrl).not.toHaveBeenCalled(); }); - it('should return no thumbnail url without service', () => { - let list = new DocumentList(null, null); - let node = new MinimalNodeEntity(); - expect(list.getThumbnailUrl(node)).toBeNull(); - }); - it('should execute action with node', () => { - let node = new MinimalNodeEntity(); - node.entry = new MinimalNodeEntryEntity(); + let node = new FileNode(); let action = new ContentActionModel(); action.handler = function () { console.log('mock handler'); @@ -211,7 +214,7 @@ describe('DocumentList', () => { }); it('should emit itemClick event', (done) => { - let node: MinimalNodeEntity = new MinimalNodeEntity(); + let node = new FileNode(); documentList.itemClick.subscribe(e => { expect(e.value).toBe(node); done(); @@ -219,25 +222,28 @@ describe('DocumentList', () => { documentList.onItemClick(node); }); - it('should prevent default events for item click', () => { + it('should prevent default item single click event', () => { spyOn(eventMock, 'preventDefault').and.stub(); documentList.onItemClick(null, eventMock); expect(eventMock.preventDefault).toHaveBeenCalled(); }); + it('should prevent default item double click event', () => { + spyOn(eventMock, 'preventDefault').and.stub(); + documentList.onItemDblClick(null, eventMock); + expect(eventMock.preventDefault).toHaveBeenCalled(); + }); + it('should display folder content on click', () => { let path = '/'; - let node = new MinimalNodeEntity(); - node.entry = new MinimalNodeEntryEntity(); - node.entry.isFolder = true; - node.entry.name = ''; + let node = new FolderNode(''); spyOn(documentList, 'getNodePath').and.returnValue(path); spyOn(documentList, 'displayFolderContent').and.stub(); - documentList.navigationMode = 'click'; + documentList.navigationMode = DocumentList.SINGLE_CLICK_NAVIGATION; documentList.onItemClick(node); expect(documentList.currentFolderPath).toBe(path); @@ -256,10 +262,7 @@ describe('DocumentList', () => { expect(documentList.navigate).toBe(true); spyOn(documentList, 'displayFolderContent').and.stub(); - let node = new MinimalNodeEntity(); - node.entry = new MinimalNodeEntryEntity(); - node.entry.isFolder = false; - + let node = new FileNode(); documentList.onItemClick(node); expect(documentList.displayFolderContent).not.toHaveBeenCalled(); @@ -268,11 +271,7 @@ describe('DocumentList', () => { it('should not display folder content on click when navigation is off', () => { spyOn(documentList, 'displayFolderContent').and.stub(); - let node = new MinimalNodeEntity(); - node.entry = new MinimalNodeEntryEntity(); - node.entry.isFolder = true; - node.entry.name = ''; - + let node = new FolderNode(''); documentList.navigate = false; documentList.onItemClick(node); @@ -310,4 +309,398 @@ describe('DocumentList', () => { expect(documentList.getObjectValue(target, 'key1.key2.key3')).toBe('value1'); }); + it('should display folder content for new folder path', () => { + spyOn(documentList, 'displayFolderContent').and.stub(); + let newPath = '/some/new/path'; + documentList.currentFolderPath = newPath; + expect(documentList.displayFolderContent).toHaveBeenCalledWith(newPath); + }); + + it('should not display folder content for same path', () => { + spyOn(documentList, 'displayFolderContent').and.stub(); + documentList.currentFolderPath = '/test'; + expect(documentList.displayFolderContent).toHaveBeenCalledTimes(1); + + documentList.currentFolderPath = '/test'; + expect(documentList.displayFolderContent).toHaveBeenCalledTimes(1); + }); + + it('should reset to default path', () => { + spyOn(documentList, 'displayFolderContent').and.stub(); + documentList.currentFolderPath = null; + + expect(documentList.currentFolderPath).toBe(documentList.DEFAULT_ROOT_FOLDER); + expect(documentList.displayFolderContent).toHaveBeenCalledWith(documentList.DEFAULT_ROOT_FOLDER); + }); + + it('should emit folder changed event', (done) => { + documentList.folderChange.subscribe(e => { + done(); + }); + documentList.folder = new PageNode(); + }); + + it('should emit folder changed event with folder details', (done) => { + let folder = new PageNode(); + let path = '/path'; + + documentList.folderChange.subscribe(e => { + expect(e.value).toBe(folder); + expect(e.path).toBe(path); + done(); + }); + + spyOn(documentList, 'displayFolderContent').and.stub(); + documentList.currentFolderPath = path; + documentList.folder = folder; + }); + + it('should not emit folder changed event', () => { + let folder = new PageNode(); + let calls = 0; + documentList.folderChange.subscribe(e => { + calls++; + }); + + documentList.folder = folder; + documentList.folder = folder; + expect(calls).toBe(1); + }); + + it('should reload on binding changes', () => { + spyOn(documentList, 'reload').and.stub(); + documentList.ngOnChanges(null); + expect(documentList.reload).toHaveBeenCalled(); + }); + + it('should execute context action on callback', () => { + let action = { + node: {}, + model: {} + }; + + spyOn(documentList, 'executeContentAction').and.stub(); + documentList.contextActionCallback(action); + expect(documentList.executeContentAction).toHaveBeenCalledWith(action.node, action.model); + }); + + it('should not execute context action on callback', () => { + spyOn(documentList, 'executeContentAction').and.stub(); + documentList.contextActionCallback(null); + expect(documentList.executeContentAction).not.toHaveBeenCalled(); + }); + + it('should upgrade material design components', () => { + documentList.ngAfterViewChecked(); + expect(componentHandler.upgradeAllRegistered).toHaveBeenCalled(); + }); + + it('should subscribe to context action handler', () => { + let value = {}; + spyOn(documentList, 'contextActionCallback').and.stub(); + documentList.ngOnInit(); + documentList.contextActionHandler.next(value); + expect(documentList.contextActionCallback).toHaveBeenCalledWith(value); + }); + + it('should suppress default context menu', () => { + spyOn(eventMock, 'preventDefault').and.stub(); + documentList.onShowContextMenu(eventMock); + expect(eventMock.preventDefault).toHaveBeenCalled(); + }); + + it('should emit file preview event on single click', (done) => { + let file = new FileNode(); + documentList.preview.subscribe(e => { + expect(e.value).toBe(file); + done(); + }); + documentList.navigationMode = DocumentList.SINGLE_CLICK_NAVIGATION; + documentList.onItemClick(file, null); + }); + + it('should emit file preview event on double click', (done) => { + let file = new FileNode(); + documentList.preview.subscribe(e => { + expect(e.value).toBe(file); + done(); + }); + documentList.navigationMode = DocumentList.DOUBLE_CLICK_NAVIGATION; + documentList.onItemDblClick(file, null); + }); + + it('should perform folder navigation on single click', () => { + let folder = new FolderNode(); + spyOn(documentList, 'performNavigation').and.stub(); + + documentList.navigationMode = DocumentList.SINGLE_CLICK_NAVIGATION; + documentList.onItemClick(folder, null); + expect(documentList.performNavigation).toHaveBeenCalled(); + }); + + it('should perform folder navigation on double click', () => { + let folder = new FolderNode(); + spyOn(documentList, 'performNavigation').and.stub(); + + documentList.navigationMode = DocumentList.DOUBLE_CLICK_NAVIGATION; + documentList.onItemDblClick(folder, null); + expect(documentList.performNavigation).toHaveBeenCalled(); + }); + + it('should not perform folder navigation on double click when single mode', () => { + let folder = new FolderNode(); + spyOn(documentList, 'performNavigation').and.stub(); + + documentList.navigationMode = DocumentList.SINGLE_CLICK_NAVIGATION; + documentList.onItemDblClick(folder, null); + + expect(documentList.performNavigation).not.toHaveBeenCalled(); + }); + + it('should not perform folder navigation on double click when navigation off', () => { + let folder = new FolderNode(); + spyOn(documentList, 'performNavigation').and.stub(); + + documentList.navigate = false; + documentList.navigationMode = DocumentList.DOUBLE_CLICK_NAVIGATION; + documentList.onItemDblClick(folder, null); + + expect(documentList.performNavigation).not.toHaveBeenCalled(); + }); + + it('should perform navigation for folder node only', () => { + let folder = new FolderNode(); + let file = new FileNode(); + spyOn(documentList, 'getNodePath').and.returnValue('/path'); + + expect(documentList.performNavigation(folder)).toBeTruthy(); + expect(documentList.performNavigation(file)).toBeFalsy(); + expect(documentList.performNavigation(null)).toBeFalsy(); + }); + + it('should not get node path for null node', () => { + expect(documentList.getNodePath(null)).toBeNull(); + }); + + it('should trim company home from node path', () => { + let file = new FileNode('file.txt'); + file.entry.path.name = '/Company Home/folder1'; + expect(documentList.getNodePath(file)).toBe('/folder1/file.txt'); + }); + + + it('should require valid node for file preview', () => { + let file = new FileNode(); + file.entry = null; + let called = false; + + documentList.navigationMode = DocumentList.SINGLE_CLICK_NAVIGATION; + documentList.preview.subscribe(val => called = true); + + documentList.onItemClick(file, null); + expect(called).toBeFalsy(); + + documentList.navigationMode = DocumentList.DOUBLE_CLICK_NAVIGATION; + documentList.onItemDblClick(file, null); + expect(called).toBeFalsy(); + }); + + it('should require valid node for folder navigation', () => { + let folder = new FolderNode(); + folder.entry = null; + spyOn(documentList, 'performNavigation').and.stub(); + + documentList.navigationMode = DocumentList.SINGLE_CLICK_NAVIGATION; + documentList.onItemClick(folder, null); + + documentList.navigationMode = DocumentList.DOUBLE_CLICK_NAVIGATION; + documentList.onItemDblClick(folder, null); + + expect(documentList.performNavigation).not.toHaveBeenCalled(); + }); + + it('should display folder content on reload', () => { + spyOn(documentList, 'displayFolderContent').and.callThrough(); + documentList.reload(); + expect(documentList.displayFolderContent).toHaveBeenCalled(); + }); + + it('should generate thumbnail for unknown content', () => { + documentList.baseComponentPath = '/root'; + let node = new FileNode(); + node.entry.isFile = false; + + expect(documentList.getThumbnailUrl(node)).toBe('/root/img/ft_ic_miscellaneous.svg'); + }); + + it('should generate folder icon path', () => { + documentList.baseComponentPath = '/root'; + let folder = new FolderNode(); + expect(documentList.getThumbnailUrl(folder)).toBe('/root/img/ft_ic_folder.svg'); + }); + + it('should generate file icon path based on mime type', () => { + let fileName = 'custom-icon.svg'; + spyOn(alfrescoServiceMock, 'getMimeTypeIcon').and.returnValue(fileName); + documentList.baseComponentPath = '/root'; + + let file = new FileNode(); + file.entry.content.mimeType = 'text/plain'; + + expect(documentList.getThumbnailUrl(file)).toBe(`/root/img/${fileName}`); + }); + + it('should fallback to default icon for missing mime type', () => { + spyOn(alfrescoServiceMock, 'getMimeTypeIcon').and.returnValue(null); + documentList.baseComponentPath = '/root'; + + let file = new FileNode(); + file.entry.content.mimeType = null; + + expect(documentList.getThumbnailUrl(file)).toBe('/root/img/ft_ic_miscellaneous.svg'); + }); + + it('should fallback to default icon for unknown mime type', () => { + spyOn(alfrescoServiceMock, 'getMimeTypeIcon').and.returnValue(null); + documentList.baseComponentPath = '/root'; + + let file = new FileNode(); + file.entry.content.mimeType = 'text/plain'; + + expect(documentList.getThumbnailUrl(file)).toBe('/root/img/ft_ic_miscellaneous.svg'); + }); + + it('should resolve thumbnail url for a file', () => { + let url = 'http://'; + spyOn(alfrescoServiceMock, 'getDocumentThumbnailUrl').and.returnValue(url); + + documentList.thumbnails = true; + + let file = new FileNode(); + expect(documentList.getThumbnailUrl(file)).toBe(url); + }); + + it('should return no thumbnail url with missing service', () => { + let list = new DocumentList(null, null); + list.thumbnails = true; + + let file = new FileNode(); + expect(list.getThumbnailUrl(file)).toBeNull(); + }); + + it('should sort on column header click', () => { + let col = new ContentColumnModel(); + col.source = 'id'; + + spyOn(documentList, 'sort').and.callThrough(); + + documentList.onColumnHeaderClick(col); + + expect(documentList.sorting).toEqual( + jasmine.objectContaining({ + key: 'id', + direction: 'asc' + }) + ); + expect(documentList.sort).toHaveBeenCalled(); + }); + + it('should invert sorting on column header click', () => { + let col = new ContentColumnModel(); + col.source = 'id'; + + spyOn(documentList, 'sort').and.callThrough(); + + documentList.sorting = { key: 'id', direction: 'asc' }; + documentList.onColumnHeaderClick(col); + + expect(documentList.sorting).toEqual( + jasmine.objectContaining({ + key: 'id', + direction: 'desc' + }) + ); + + documentList.onColumnHeaderClick(col); + expect(documentList.sorting).toEqual( + jasmine.objectContaining({ + key: 'id', + direction: 'asc' + }) + ); + + expect(documentList.sort).toHaveBeenCalledTimes(2); + }); + + it('should use ascending direction for different column header click', () => { + let col = new ContentColumnModel(); + col.source = 'id'; + + spyOn(documentList, 'sort').and.callThrough(); + + documentList.sorting = { key: 'col1', direction: 'desc' }; + documentList.onColumnHeaderClick(col); + + expect(documentList.sorting).toEqual( + jasmine.objectContaining({ + key: 'id', + direction: 'asc' + }) + ); + + expect(documentList.sort).toHaveBeenCalled(); + }); + + it('should not sort by column header when instance is missing', () => { + spyOn(documentList, 'sort').and.callThrough(); + documentList.onColumnHeaderClick(null); + expect(documentList.sort).not.toHaveBeenCalled(); + }); + + it('should convert cell value to formatted date', () => { + + let rawValue = new Date(2015, 6, 15, 21, 43, 11).toString(); // Wed Jul 15 2015 21:43:11 GMT+0100 (BST); + let dateValue = 'Jul 15, 2015, 9:43:11 PM'; + + let file = new FileNode(); + file.entry.createdAt = rawValue; + + let col = new ContentColumnModel(); + col.source = 'createdAt'; + col.type = 'date'; + col.format = 'medium'; // Jul 15, 2015, 9:43:11 PM + + let value = documentList.getCellValue(file, col); + expect(value).toBe(dateValue); + + }); + + it('should return date value as string', () => { + let rawValue = new Date(2015, 6, 15, 21, 43, 11).toString(); // Wed Jul 15 2015 21:43:11 GMT+0100 (BST); + + let file = new FileNode(); + file.entry.createdAt = rawValue; + + let col = new ContentColumnModel(); + col.source = 'createdAt'; + col.type = 'string'; + + let value = documentList.getCellValue(file, col); + expect(value).toBe(rawValue); + }); + + it('should convert cell value to thumbnail', () => { + let url = 'http://
'; + spyOn(documentList, 'getThumbnailUrl').and.returnValue(url); + + let file = new FileNode(); + + let col = new ContentColumnModel(); + col.source = '$thumbnail'; + col.type = 'image'; + + let value = documentList.getCellValue(file, col); + expect(value).toBe(url); + }); + }); diff --git a/ng2-components/ng2-alfresco-documentlist/src/components/document-list.ts b/ng2-components/ng2-alfresco-documentlist/src/components/document-list.ts index 6075827aaa..9c993ec516 100644 --- a/ng2-components/ng2-alfresco-documentlist/src/components/document-list.ts +++ b/ng2-components/ng2-alfresco-documentlist/src/components/document-list.ts @@ -52,6 +52,9 @@ declare let __moduleName: string; }) export class DocumentList implements OnInit, AfterViewChecked, AfterContentInit, OnChanges { + static SINGLE_CLICK_NAVIGATION: string = 'click'; + static DOUBLE_CLICK_NAVIGATION: string = 'dblclick'; + DEFAULT_ROOT_FOLDER: string = '/'; baseComponentPath = __moduleName.replace('/components/document-list.js', ''); @@ -216,7 +219,7 @@ export class DocumentList implements OnInit, AfterViewChecked, AfterContentInit, value: item }); - if (this.navigate && this.navigationMode === 'click') { + if (this.navigate && this.navigationMode === DocumentList.SINGLE_CLICK_NAVIGATION) { if (item && item.entry) { if (item.entry.isFile) { this.preview.emit({ @@ -240,7 +243,7 @@ export class DocumentList implements OnInit, AfterViewChecked, AfterContentInit, value: item }); - if (this.navigate && this.navigationMode === 'dblclick') { + if (this.navigate && this.navigationMode === DocumentList.DOUBLE_CLICK_NAVIGATION) { if (item && item.entry) { if (item.entry.isFile) { this.preview.emit({ @@ -261,10 +264,12 @@ export class DocumentList implements OnInit, AfterViewChecked, AfterContentInit, } } - private performNavigation(node: MinimalNodeEntity) { + performNavigation(node: MinimalNodeEntity): boolean { if (node && node.entry && node.entry.isFolder) { this.currentFolderPath = this.getNodePath(node); + return true; } + return false; } /** @@ -290,7 +295,9 @@ export class DocumentList implements OnInit, AfterViewChecked, AfterContentInit, if (entry.content && entry.content.mimeType) { let icon = this.alfrescoService.getMimeTypeIcon(entry.content.mimeType); - return `${this.baseComponentPath}/img/${icon}`; + if (icon) { + return `${this.baseComponentPath}/img/${icon}`; + } } } diff --git a/ng2-components/ng2-alfresco-documentlist/src/models/document-library.model.ts b/ng2-components/ng2-alfresco-documentlist/src/models/document-library.model.ts index 7f72a7e04b..71cab49dcb 100644 --- a/ng2-components/ng2-alfresco-documentlist/src/models/document-library.model.ts +++ b/ng2-components/ng2-alfresco-documentlist/src/models/document-library.model.ts @@ -17,59 +17,6 @@ // note: contains only limited subset of available fields -// TODO: deprecated -export class FolderEntity { - items: DocumentEntity[]; -} - -// TODO: deprecated -export class DocumentEntity { - nodeRef: string; - nodeType: string; - type: string; - mimetype: string; - isFolder: boolean; - isLink: boolean; - fileName: string; - displayName: string; - status: string; - title: string; - description: string; - author: string; - createdOn: string; - createdBy: string; - createdByUser: string; - modifiedOn: string; - modifiedBy: string; - modifiedByUser: string; - lockedBy: string; - lockedByUser: string; - size: number; - version: string; - contentUrl: string; - webdavUrl: string; - actionSet: string; - tags: string[]; - activeWorkflows: string; - location: LocationEntity; -} - -// TODO: deprecated -export class LocationEntity { - repositoryId: string; - site: string; - siteTitle: string; - container: string; - path: string; - file: string; - parent: LocationParentEntity; -} - -// TODO: deprecated -export class LocationParentEntity { - nodeRef: string; -} - export class NodePaging { list: NodePagingList; }