Unit tests for document list

This commit is contained in:
Denys Vuika
2016-06-28 15:10:05 +01:00
parent 97c9905046
commit bf6947abf9
4 changed files with 487 additions and 87 deletions

View File

@@ -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();
}
}

View File

@@ -21,28 +21,40 @@ import {
expect, expect,
beforeEach beforeEach
} from '@angular/core/testing'; } from '@angular/core/testing';
import { NgZone } from '@angular/core';
import { DocumentList } from './document-list'; import { DocumentList } from './document-list';
import { ContentColumnModel } from '../models/content-column.model'; import { ContentColumnModel } from '../models/content-column.model';
import { AlfrescoServiceMock } from '../assets/alfresco.service.mock'; import { AlfrescoServiceMock } from '../assets/alfresco.service.mock';
import { MinimalNodeEntity, MinimalNodeEntryEntity } from '../models/document-library.model';
import { ContentActionModel } from '../models/content-action.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', () => { describe('DocumentList', () => {
let alfrescoServiceMock: AlfrescoServiceMock; let alfrescoServiceMock: AlfrescoServiceMock;
let documentList: DocumentList; let documentList: DocumentList;
let eventMock: any; let eventMock: any;
let componentHandler;
beforeEach(() => { beforeEach(() => {
alfrescoServiceMock = new AlfrescoServiceMock(); alfrescoServiceMock = new AlfrescoServiceMock();
documentList = new DocumentList(alfrescoServiceMock, null); let zone = new NgZone(false);
documentList = new DocumentList(alfrescoServiceMock, zone);
eventMock = { eventMock = {
preventDefault: function () { preventDefault: function () {
console.log('mock preventDefault'); console.log('mock preventDefault');
} }
}; };
componentHandler = jasmine.createSpyObj('componentHandler', [
'upgradeAllRegistered'
]);
window['componentHandler'] = componentHandler;
}); });
it('should setup default columns', () => { it('should setup default columns', () => {
@@ -84,9 +96,7 @@ describe('DocumentList', () => {
let url = 'URL'; let url = 'URL';
spyOn(alfrescoServiceMock, 'getDocumentThumbnailUrl').and.returnValue(url); spyOn(alfrescoServiceMock, 'getDocumentThumbnailUrl').and.returnValue(url);
let node = new MinimalNodeEntity(); let node = new FileNode();
node.entry = new MinimalNodeEntryEntity();
node.entry.isFile = true;
documentList.thumbnails = true; documentList.thumbnails = true;
let result = documentList.getThumbnailUrl(node); let result = documentList.getThumbnailUrl(node);
@@ -104,15 +114,8 @@ describe('DocumentList', () => {
expect(alfrescoServiceMock.getDocumentThumbnailUrl).not.toHaveBeenCalled(); 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', () => { it('should execute action with node', () => {
let node = new MinimalNodeEntity(); let node = new FileNode();
node.entry = new MinimalNodeEntryEntity();
let action = new ContentActionModel(); let action = new ContentActionModel();
action.handler = function () { action.handler = function () {
console.log('mock handler'); console.log('mock handler');
@@ -211,7 +214,7 @@ describe('DocumentList', () => {
}); });
it('should emit itemClick event', (done) => { it('should emit itemClick event', (done) => {
let node: MinimalNodeEntity = new MinimalNodeEntity(); let node = new FileNode();
documentList.itemClick.subscribe(e => { documentList.itemClick.subscribe(e => {
expect(e.value).toBe(node); expect(e.value).toBe(node);
done(); done();
@@ -219,25 +222,28 @@ describe('DocumentList', () => {
documentList.onItemClick(node); documentList.onItemClick(node);
}); });
it('should prevent default events for item click', () => { it('should prevent default item single click event', () => {
spyOn(eventMock, 'preventDefault').and.stub(); spyOn(eventMock, 'preventDefault').and.stub();
documentList.onItemClick(null, eventMock); documentList.onItemClick(null, eventMock);
expect(eventMock.preventDefault).toHaveBeenCalled(); 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', () => { it('should display folder content on click', () => {
let path = '/'; let path = '/';
let node = new MinimalNodeEntity(); let node = new FolderNode('<display name>');
node.entry = new MinimalNodeEntryEntity();
node.entry.isFolder = true;
node.entry.name = '<display name>';
spyOn(documentList, 'getNodePath').and.returnValue(path); spyOn(documentList, 'getNodePath').and.returnValue(path);
spyOn(documentList, 'displayFolderContent').and.stub(); spyOn(documentList, 'displayFolderContent').and.stub();
documentList.navigationMode = 'click'; documentList.navigationMode = DocumentList.SINGLE_CLICK_NAVIGATION;
documentList.onItemClick(node); documentList.onItemClick(node);
expect(documentList.currentFolderPath).toBe(path); expect(documentList.currentFolderPath).toBe(path);
@@ -256,10 +262,7 @@ describe('DocumentList', () => {
expect(documentList.navigate).toBe(true); expect(documentList.navigate).toBe(true);
spyOn(documentList, 'displayFolderContent').and.stub(); spyOn(documentList, 'displayFolderContent').and.stub();
let node = new MinimalNodeEntity(); let node = new FileNode();
node.entry = new MinimalNodeEntryEntity();
node.entry.isFolder = false;
documentList.onItemClick(node); documentList.onItemClick(node);
expect(documentList.displayFolderContent).not.toHaveBeenCalled(); expect(documentList.displayFolderContent).not.toHaveBeenCalled();
@@ -268,11 +271,7 @@ describe('DocumentList', () => {
it('should not display folder content on click when navigation is off', () => { it('should not display folder content on click when navigation is off', () => {
spyOn(documentList, 'displayFolderContent').and.stub(); spyOn(documentList, 'displayFolderContent').and.stub();
let node = new MinimalNodeEntity(); let node = new FolderNode('<display name>');
node.entry = new MinimalNodeEntryEntity();
node.entry.isFolder = true;
node.entry.name = '<display name>';
documentList.navigate = false; documentList.navigate = false;
documentList.onItemClick(node); documentList.onItemClick(node);
@@ -310,4 +309,398 @@ describe('DocumentList', () => {
expect(documentList.getObjectValue(target, 'key1.key2.key3')).toBe('value1'); 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://<some url>';
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 = <ColumnSortingModel> { 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 = <ColumnSortingModel> { 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://<address>';
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);
});
}); });

View File

@@ -52,6 +52,9 @@ declare let __moduleName: string;
}) })
export class DocumentList implements OnInit, AfterViewChecked, AfterContentInit, OnChanges { export class DocumentList implements OnInit, AfterViewChecked, AfterContentInit, OnChanges {
static SINGLE_CLICK_NAVIGATION: string = 'click';
static DOUBLE_CLICK_NAVIGATION: string = 'dblclick';
DEFAULT_ROOT_FOLDER: string = '/'; DEFAULT_ROOT_FOLDER: string = '/';
baseComponentPath = __moduleName.replace('/components/document-list.js', ''); baseComponentPath = __moduleName.replace('/components/document-list.js', '');
@@ -216,7 +219,7 @@ export class DocumentList implements OnInit, AfterViewChecked, AfterContentInit,
value: item value: item
}); });
if (this.navigate && this.navigationMode === 'click') { if (this.navigate && this.navigationMode === DocumentList.SINGLE_CLICK_NAVIGATION) {
if (item && item.entry) { if (item && item.entry) {
if (item.entry.isFile) { if (item.entry.isFile) {
this.preview.emit({ this.preview.emit({
@@ -240,7 +243,7 @@ export class DocumentList implements OnInit, AfterViewChecked, AfterContentInit,
value: item value: item
}); });
if (this.navigate && this.navigationMode === 'dblclick') { if (this.navigate && this.navigationMode === DocumentList.DOUBLE_CLICK_NAVIGATION) {
if (item && item.entry) { if (item && item.entry) {
if (item.entry.isFile) { if (item.entry.isFile) {
this.preview.emit({ 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) { if (node && node.entry && node.entry.isFolder) {
this.currentFolderPath = this.getNodePath(node); 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) { if (entry.content && entry.content.mimeType) {
let icon = this.alfrescoService.getMimeTypeIcon(entry.content.mimeType); let icon = this.alfrescoService.getMimeTypeIcon(entry.content.mimeType);
return `${this.baseComponentPath}/img/${icon}`; if (icon) {
return `${this.baseComponentPath}/img/${icon}`;
}
} }
} }

View File

@@ -17,59 +17,6 @@
// note: contains only limited subset of available fields // 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 { export class NodePaging {
list: NodePagingList; list: NodePagingList;
} }