diff --git a/ng2-components/ng2-alfresco-documentlist/demo/browser-sync-config.js b/ng2-components/ng2-alfresco-documentlist/demo/browser-sync-config.js deleted file mode 100644 index bd5f084318..0000000000 --- a/ng2-components/ng2-alfresco-documentlist/demo/browser-sync-config.js +++ /dev/null @@ -1,23 +0,0 @@ -var browserSync = require("browser-sync").create(); -var historyApiFallback = require('connect-history-api-fallback'); - -browserSync.init({ - - server: { - baseDir: './', - middleware: [ historyApiFallback() ] - }, - - files: [ - 'index.html', - 'dist/**/*.{html,htm,css,js}', - 'node_modules/ng2-alfresco-core/dist/**/*.{html,htm,css,js}', - 'node_modules/ng2-alfresco-datatable/dist/**/*.{html,htm,css,js}', - 'node_modules/ng2-alfresco-documentlist/dist/**/*.{html,htm,css,js}', - 'node_modules/ng2-alfresco-login/dist/**/*.{html,htm,css,js}', - 'node_modules/ng2-alfresco-search/dist/**/*.{html,htm,css,js}', - 'node_modules/ng2-alfresco-upload/dist/**/*.{html,htm,css,js}', - 'node_modules/ng2-alfresco-viewer/dist/**/*.{html,htm,css,js}'], - - reloadDelay: 1000 -}); diff --git a/ng2-components/ng2-alfresco-documentlist/demo/package.json b/ng2-components/ng2-alfresco-documentlist/demo/package.json index b2839dc96d..c179dddd8b 100644 --- a/ng2-components/ng2-alfresco-documentlist/demo/package.json +++ b/ng2-components/ng2-alfresco-documentlist/demo/package.json @@ -8,8 +8,8 @@ "clean": "rimraf dist node_modules typings", "typings": "typings install", "postinstall": "npm run typings && npm run build", - "start": "npm install && npm run typings && concurrently \"npm run build:w\" \"npm run server\" ", - "server": "node browser-sync-config.js", + "start": "concurrently \"npm run build:w\" \"npm run server\" ", + "server": "wsrv -o -s", "build": "npm run tslint && rimraf dist && tsc", "build:w": "npm run tslint && rimraf dist && tsc -w", "tslint": "npm run tslint-src && npm run tslint-root", @@ -44,12 +44,11 @@ "ng2-alfresco-documentlist": "0.1.34" }, "devDependencies": { - "browser-sync": "2.10.0", - "connect-history-api-fallback": "1.2.0", "concurrently": "2.0.0", "tslint": "3.8.1", "typescript": "1.8.10", - "typings": "1.0.4" + "typings": "1.0.4", + "wsrv": "0.1.3" }, "keywords": [ "angular2", diff --git a/ng2-components/ng2-alfresco-documentlist/package.json b/ng2-components/ng2-alfresco-documentlist/package.json index bf9be0469f..44c3bd07e1 100644 --- a/ng2-components/ng2-alfresco-documentlist/package.json +++ b/ng2-components/ng2-alfresco-documentlist/package.json @@ -6,7 +6,6 @@ "scripts": { "clean": "rimraf dist node_modules typings", "typings": "typings install", - "server": "http-server -c-1 -o -p 8875 .", "build": "npm run tslint && typings install && rimraf dist && tsc && npm run copytemplates && license-check", "build:w": "npm run tslint && typings install && rimraf dist && npm run watch-task", "watch-task": "concurrently \"npm run tsc:w\" \"npm run copytemplates:w\" \"license-check\"", @@ -27,7 +26,7 @@ "test": "karma start karma.conf.js --reporters mocha,coverage --single-run", "test-browser": "concurrently \"karma start karma.conf.js --reporters kjhtml\" \"npm run watch-task\"", "posttest": "node_modules/.bin/remap-istanbul -i coverage/report/coverage-final.json -o coverage/report -t html", - "coverage": "npm run test && http-server -c-1 -o -p 9875 ./coverage/report", + "coverage": "npm run test && wsrv -o -p 9875 ./coverage/report", "prepublish": "npm run build" }, "main": "./dist/index.js", @@ -85,7 +84,6 @@ "concurrently": "2.1.0", "coveralls": "2.11.9", "cpx": "1.3.1", - "http-server": "0.8.5", "jasmine-core": "2.4.1", "karma": "0.13.22", "karma-chrome-launcher": "1.0.1", @@ -98,7 +96,8 @@ "traceur": "0.0.91", "tslint": "3.8.1", "typescript": "1.8.10", - "typings": "1.0.4" + "typings": "1.0.4", + "wsrv": "0.1.3" }, "license-check-config": { "src": [ diff --git a/ng2-components/ng2-alfresco-documentlist/src/assets/alfresco.service.mock.ts b/ng2-components/ng2-alfresco-documentlist/src/assets/alfresco.service.mock.ts index dbeae24ba2..179c919e81 100644 --- a/ng2-components/ng2-alfresco-documentlist/src/assets/alfresco.service.mock.ts +++ b/ng2-components/ng2-alfresco-documentlist/src/assets/alfresco.service.mock.ts @@ -16,7 +16,6 @@ */ import {Observable} from 'rxjs/Observable'; -import { MinimalNodeEntity } from '../models/document-library.model'; import {AlfrescoService} from '../../src/services/alfresco.service'; import { AlfrescoSettingsService, @@ -29,9 +28,9 @@ export class AlfrescoServiceMock extends AlfrescoService { _folderToReturn: any = {}; constructor( - settings: AlfrescoSettingsService = null, - authService: AlfrescoAuthenticationService = null, - contentService: AlfrescoContentService = null + settings?: AlfrescoSettingsService, + authService?: AlfrescoAuthenticationService, + contentService?: AlfrescoContentService ) { super(settings, authService, contentService); } @@ -42,8 +41,4 @@ export class AlfrescoServiceMock extends AlfrescoService { observer.complete(); }); } - - getDocumentThumbnailUrl(folder: MinimalNodeEntity) { - return ''; - } } 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/content-action.spec.ts b/ng2-components/ng2-alfresco-documentlist/src/components/content-action.spec.ts index 010bf39984..08d260980c 100644 --- a/ng2-components/ng2-alfresco-documentlist/src/components/content-action.spec.ts +++ b/ng2-components/ng2-alfresco-documentlist/src/components/content-action.spec.ts @@ -21,23 +21,28 @@ import { expect, beforeEach } from '@angular/core/testing'; -import {EventEmitter} from '@angular/core'; +import { EventEmitter } from '@angular/core'; -import {DocumentList} from './document-list'; -import {AlfrescoServiceMock} from '../assets/alfresco.service.mock'; - -import {ContentActionList} from './content-action-list'; -import {ContentAction} from './content-action'; -import {DocumentActionsService} from '../services/document-actions.service'; -import {FolderActionsService} from '../services/folder-actions.service'; +import { DocumentList } from './document-list'; +import { AlfrescoServiceMock } from '../assets/alfresco.service.mock'; +import { ContentActionList } from './content-action-list'; +import { ContentAction } from './content-action'; +import { DocumentActionsService } from '../services/document-actions.service'; +import { FolderActionsService } from '../services/folder-actions.service'; +import { ContentActionHandler } from '../models/content-action.model'; describe('ContentAction', () => { let documentList: DocumentList; let actionList: ContentActionList; + let documentActions: DocumentActionsService; + let folderActions: FolderActionsService; beforeEach(() => { let alfrescoServiceMock = new AlfrescoServiceMock(); + documentActions = new DocumentActionsService(null, null); + folderActions = new FolderActionsService(null); + documentList = new DocumentList(alfrescoServiceMock, null); actionList = new ContentActionList(documentList); }); @@ -73,7 +78,6 @@ describe('ContentAction', () => { it('should get action handler from document actions service', () => { let handler = function() {}; - let documentActions = new DocumentActionsService(null); spyOn(documentActions, 'getHandler').and.returnValue(handler); let action = new ContentAction(actionList, documentActions, null); @@ -91,7 +95,6 @@ describe('ContentAction', () => { it('should get action handler from folder actions service', () => { let handler = function() {}; - let folderActions = new FolderActionsService(); spyOn(folderActions, 'getHandler').and.returnValue(handler); let action = new ContentAction(actionList, null, folderActions); @@ -108,10 +111,7 @@ describe('ContentAction', () => { }); it('should require target to get system handler', () => { - let folderActions = new FolderActionsService(); spyOn(folderActions, 'getHandler').and.stub(); - - let documentActions = new DocumentActionsService(null); spyOn(documentActions, 'getHandler').and.stub(); let action = new ContentAction(actionList, documentActions, folderActions); @@ -133,7 +133,6 @@ describe('ContentAction', () => { }); it('should be case insensitive for document target', () => { - let documentActions = new DocumentActionsService(null); spyOn(documentActions, 'getHandler').and.stub(); let action = new ContentAction(actionList, documentActions, null); @@ -146,7 +145,6 @@ describe('ContentAction', () => { }); it('should be case insensitive for folder target', () => { - let folderActions = new FolderActionsService(); spyOn(folderActions, 'getHandler').and.stub(); let action = new ContentAction(actionList, null, folderActions); @@ -177,4 +175,54 @@ describe('ContentAction', () => { let model = documentList.actions[0]; model.handler(''); }); + + it('should sync localizable fields with model', () => { + + let action = new ContentAction(actionList, null, null); + action.title = 'title1'; + action.ngOnInit(); + + expect(action.model.title).toBe(action.title); + + action.title = 'title2'; + action.ngOnChanges(null); + + expect(action.model.title).toBe('title2'); + }); + + it('should not find document action handler with missing service', () => { + let action = new ContentAction(actionList, null, null); + expect(action.getSystemHandler('document', 'name')).toBeNull(); + }); + + it('should not find folder action handler with missing service', () => { + let action = new ContentAction(actionList, null, null); + expect(action.getSystemHandler('folder', 'name')).toBeNull(); + }); + + it('should find document action handler via service', () => { + let handler = function (obj: any, target?: any) {}; + let action = new ContentAction(actionList, documentActions, null); + spyOn(documentActions, 'getHandler').and.returnValue(handler); + expect(action.getSystemHandler('document', 'name')).toBe(handler); + }); + + it('should find folder action handler via service', () => { + let handler = function (obj: any, target?: any) {}; + let action = new ContentAction(actionList, null, folderActions); + spyOn(folderActions, 'getHandler').and.returnValue(handler); + expect(action.getSystemHandler('folder', 'name')).toBe(handler); + }); + + it('should not find actions for unknown target type', () => { + spyOn(folderActions, 'getHandler').and.stub(); + spyOn(documentActions, 'getHandler').and.stub(); + + let action = new ContentAction(actionList, documentActions, folderActions); + + expect(action.getSystemHandler('unknown', 'name')).toBeNull(); + expect(folderActions.getHandler).not.toHaveBeenCalled(); + expect(documentActions.getHandler).not.toHaveBeenCalled(); + + }); }); diff --git a/ng2-components/ng2-alfresco-documentlist/src/components/content-action.ts b/ng2-components/ng2-alfresco-documentlist/src/components/content-action.ts index 1bbff3ee6c..33164fdea5 100644 --- a/ng2-components/ng2-alfresco-documentlist/src/components/content-action.ts +++ b/ng2-components/ng2-alfresco-documentlist/src/components/content-action.ts @@ -76,12 +76,12 @@ export class ContentAction implements OnInit, OnChanges { this.list.registerAction(this.model); } - ngOnChanges() { + ngOnChanges(changes) { // update localizable properties this.model.title = this.title; } - private getSystemHandler(target: string, name: string): ContentActionHandler { + getSystemHandler(target: string, name: string): ContentActionHandler { if (target) { let ltarget = target.toLowerCase(); diff --git a/ng2-components/ng2-alfresco-documentlist/src/components/content-column.spec.ts b/ng2-components/ng2-alfresco-documentlist/src/components/content-column.spec.ts index b3aa603c31..9f335ec6a3 100644 --- a/ng2-components/ng2-alfresco-documentlist/src/components/content-column.spec.ts +++ b/ng2-components/ng2-alfresco-documentlist/src/components/content-column.spec.ts @@ -77,4 +77,20 @@ describe('ContentColumn', () => { expect(model.srTitle).toBe('Thumbnail'); }); + it('should sync localizable fields with model', () => { + + let column = new ContentColumn(columnList); + column.title = 'title1'; + column.srTitle = 'srTitle1'; + column.ngOnInit(); + + expect(column.model.title).toBe(column.title); + expect(column.model.srTitle).toBe(column.srTitle); + + column.title = 'title2'; + column.ngOnChanges(null); + + expect(column.model.title).toBe('title2'); + }); + }); diff --git a/ng2-components/ng2-alfresco-documentlist/src/components/document-list-breadcrumb.component.spec.ts b/ng2-components/ng2-alfresco-documentlist/src/components/document-list-breadcrumb.component.spec.ts new file mode 100644 index 0000000000..9b1ba0fde1 --- /dev/null +++ b/ng2-components/ng2-alfresco-documentlist/src/components/document-list-breadcrumb.component.spec.ts @@ -0,0 +1,109 @@ +/*! + * @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 { + it, + describe, + beforeEach, + expect +} from '@angular/core/testing'; + +import { + DocumentListBreadcrumb, + PathNode +} from './document-list-breadcrumb.component'; +import { DocumentList } from './document-list'; + +describe('DocumentListBreadcrumb', () => { + + let component; + + beforeEach(() => { + component = new DocumentListBreadcrumb(); + }); + + it('should set current path', () => { + let path = '/some/path'; + component.currentFolderPath = path; + expect(component.currentFolderPath).toBe(path); + }); + + it('should init with root folder by default', () => { + expect(component.route.length).toBe(1); + expect(component.route[0]).toEqual( + jasmine.objectContaining({ + name: 'Root', + path: '/' + }) + ); + }); + + it('should fallback to default root for invalid path', () => { + component.currentFolderPath = null; + expect(component.currentFolderPath).toBe('/'); + + expect(component.route.length).toBe(1); + expect(component.route[0]).toEqual( + jasmine.objectContaining({ + name: 'Root', + path: '/' + }) + ); + }); + + it('should parse the route', () => { + component.currentFolderPath = '/some/path'; + + expect(component.route.length).toBe(3); + expect(component.route).toEqual( + jasmine.objectContaining([ + { name: 'Root', path: '/' }, + { name: 'some', path: '/some' }, + { name: 'path', path: '/some/path' } + ]) + ); + }); + + it('should prevent default click behavior', () => { + let event = jasmine.createSpyObj('event', ['preventDefault']); + component.onRoutePathClick(null, event); + expect(event.preventDefault).toHaveBeenCalled(); + }); + + it('should emit navigation event', (done) => { + let node = { name: 'name', path: '/path' }; + component.navigate.subscribe(val => { + expect(val.value.name).toBe(node.name); + expect(val.value.path).toBe(node.path); + done(); + }); + + component.onRoutePathClick(node, null); + }); + + it('should update document list on click', () => { + let documentList = new DocumentList(null, null); + spyOn(documentList, 'displayFolderContent').and.stub(); + + let node = { name: 'name', path: '/path' }; + component.target = documentList; + + component.onRoutePathClick(node, null); + expect(documentList.currentFolderPath).toBe(node.path); + }); + +}); diff --git a/ng2-components/ng2-alfresco-documentlist/src/components/document-list-breadcrumb.component.ts b/ng2-components/ng2-alfresco-documentlist/src/components/document-list-breadcrumb.component.ts index 16d086dfca..5bc4990259 100644 --- a/ng2-components/ng2-alfresco-documentlist/src/components/document-list-breadcrumb.component.ts +++ b/ng2-components/ng2-alfresco-documentlist/src/components/document-list-breadcrumb.component.ts @@ -42,11 +42,11 @@ export class DocumentListBreadcrumb { @Input() set currentFolderPath(val: string) { if (this._currentFolderPath !== val) { - this._currentFolderPath = val; - if (val) { + this._currentFolderPath = val; this.route = this.parsePath(val); } else { + this._currentFolderPath = this.rootFolder.path; this.route = [ this.rootFolder ]; } } 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/content-column.model.spec.ts b/ng2-components/ng2-alfresco-documentlist/src/models/content-column.model.spec.ts new file mode 100644 index 0000000000..f030113cdc --- /dev/null +++ b/ng2-components/ng2-alfresco-documentlist/src/models/content-column.model.spec.ts @@ -0,0 +1,37 @@ +/*! + * @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 { + it, + describe, + expect +} from '@angular/core/testing'; + +import { ContentColumnModel } from './content-column.model'; + +describe('ContentColumnModel', () => { + + it('should init with text type from config object', () => { + let model = new ContentColumnModel({}); + expect(model.type).toBe(ContentColumnModel.TYPE_TEXT); + }); + + it('should return supported types', () => { + expect(ContentColumnModel.getSupportedTypes().length).toBeGreaterThan(0); + }); + +}); diff --git a/ng2-components/ng2-alfresco-documentlist/src/models/content-column.model.ts b/ng2-components/ng2-alfresco-documentlist/src/models/content-column.model.ts index 3ea69b3b12..e5612eb011 100644 --- a/ng2-components/ng2-alfresco-documentlist/src/models/content-column.model.ts +++ b/ng2-components/ng2-alfresco-documentlist/src/models/content-column.model.ts @@ -16,11 +16,17 @@ */ export class ContentColumnModel { + + static TYPE_TEXT: string = 'text'; + static TYPE_DATE: string = 'date'; + static TYPE_IMAGE: string = 'image'; + // static TYPE_NUMBER: string = 'number'; + title: string; srTitle: string; source: string; cssClass: string; - type: string = 'text'; // text|date|image|number + type: string = ContentColumnModel.TYPE_TEXT; format: string = 'medium'; constructor(obj?: any) { @@ -29,8 +35,17 @@ export class ContentColumnModel { this.srTitle = obj.srTitle; this.source = obj.source; this.cssClass = obj.cssClass; - this.type = obj.type || 'text'; + this.type = obj.type || ContentColumnModel.TYPE_TEXT; this.format = obj.format; } } + + static getSupportedTypes(): string[] { + return [ + ContentColumnModel.TYPE_TEXT, + ContentColumnModel.TYPE_DATE, + ContentColumnModel.TYPE_IMAGE + // ContentColumnModel.TYPE_NUMBER + ]; + } } 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; }