diff --git a/package-lock.json b/package-lock.json index d9eff179c..969ab6f43 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1295,9 +1295,9 @@ "dev": true }, "@types/jasmine": { - "version": "2.5.53", - "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-2.5.53.tgz", - "integrity": "sha512-2YNL0jXYuN7w07mb1sMZQ6T6zOvGi83v8UbjhBZ8mhvI1VkQ2STU9XOrTFyvWswMyh5rW1evS+e7qltYJvTqPA==", + "version": "3.5.10", + "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-3.5.10.tgz", + "integrity": "sha512-3F8qpwBAiVc5+HPJeXJpbrl+XjawGmciN5LgiO7Gv1pl1RHtjoMNqZpqEksaPJW05ViKe8snYInRs6xB25Xdew==", "dev": true }, "@types/jasminewd2": { diff --git a/package.json b/package.json index 969fab89d..e1cca0958 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,7 @@ "@angular/cli": "^7.3.9", "@angular/compiler-cli": "7.2.15", "@angular/language-service": "7.2.15", - "@types/jasmine": "2.5.53", + "@types/jasmine": "3.5.10", "@types/jasminewd2": "^2.0.8", "@types/node": "^13.11.0", "@types/selenium-webdriver": "^4.0.9", diff --git a/projects/aca-shared/rules/src/app.rules.spec.ts b/projects/aca-shared/rules/src/app.rules.spec.ts index d04750627..06d352f25 100644 --- a/projects/aca-shared/rules/src/app.rules.spec.ts +++ b/projects/aca-shared/rules/src/app.rules.spec.ts @@ -472,7 +472,7 @@ describe('app.evaluators', () => { withCredentials: false }; - expect(app.canShowLanguagePicker(context)).toBe(true); + expect(app.canShowLogout(context)).toBe(true); }); }); }); diff --git a/src/app/components/context-menu/context-menu.service.ts b/src/app/components/context-menu/context-menu.service.ts index 204661ca2..38f685028 100644 --- a/src/app/components/context-menu/context-menu.service.ts +++ b/src/app/components/context-menu/context-menu.service.ts @@ -51,7 +51,7 @@ export class ContextMenuService { }); } - open(config: ContextmenuOverlayConfig) { + open(config: ContextmenuOverlayConfig): ContextMenuOverlayRef { const overlay = this.createOverlay(config); const overlayRef = new ContextMenuOverlayRef(overlay); @@ -60,7 +60,7 @@ export class ContextMenuService { return overlayRef; } - private createOverlay(config: ContextmenuOverlayConfig) { + private createOverlay(config: ContextmenuOverlayConfig): OverlayRef { const overlayConfig = this.getOverlayConfig(config); return this.overlay.create(overlayConfig); } @@ -68,7 +68,7 @@ export class ContextMenuService { private attachDialogContainer( overlay: OverlayRef, contextmenuOverlayRef: ContextMenuOverlayRef - ) { + ): ContextMenuComponent { const injector = this.createInjector(contextmenuOverlayRef); const containerPortal = new ComponentPortal( diff --git a/src/app/components/favorite-libraries/favorite-libraries.component.spec.ts b/src/app/components/favorite-libraries/favorite-libraries.component.spec.ts index a9d20c12a..e31d4ac5d 100644 --- a/src/app/components/favorite-libraries/favorite-libraries.component.spec.ts +++ b/src/app/components/favorite-libraries/favorite-libraries.component.spec.ts @@ -42,6 +42,7 @@ import { EffectsModule } from '@ngrx/effects'; import { RouterEffects } from '@alfresco/aca-shared/store'; import { of, throwError } from 'rxjs'; import { LibraryEffects } from '../../store/effects'; +import { NodeEntry } from '@alfresco/js-api'; describe('FavoriteLibrariesComponent', () => { let fixture: ComponentFixture; @@ -90,7 +91,7 @@ describe('FavoriteLibrariesComponent', () => { router = TestBed.get(Router); spyOn(contentApiService, 'getNode').and.returnValue( - of({ entry: { id: 'libraryId' } }) + of({ entry: { id: 'libraryId' } } as NodeEntry) ); }); diff --git a/src/app/components/info-drawer/library-metadata-tab/library-metadata-form.component.spec.ts b/src/app/components/info-drawer/library-metadata-tab/library-metadata-form.component.spec.ts index 00041c066..a79586c84 100644 --- a/src/app/components/info-drawer/library-metadata-tab/library-metadata-form.component.spec.ts +++ b/src/app/components/info-drawer/library-metadata-tab/library-metadata-form.component.spec.ts @@ -33,7 +33,7 @@ import { Store } from '@ngrx/store'; import { UpdateLibraryAction } from '@alfresco/aca-shared/store'; import { AppTestingModule } from '../../../testing/app-testing.module'; import { NO_ERRORS_SCHEMA } from '@angular/core'; -import { Site } from '@alfresco/js-api'; +import { Site, SitePaging } from '@alfresco/js-api'; import { AlfrescoApiService, AlfrescoApiServiceMock } from '@alfresco/adf-core'; describe('LibraryMetadataFormComponent', () => { @@ -231,7 +231,7 @@ describe('LibraryMetadataFormComponent', () => { ).and.returnValue( Promise.resolve({ list: { entries: [{ entry: { title } }] } - }) + } as SitePaging) ); const siteEntryModel = { @@ -262,7 +262,7 @@ describe('LibraryMetadataFormComponent', () => { ).and.returnValue( Promise.resolve({ list: { entries: [{ entry: { title: 'libraryTitle' } }] } - }) + } as SitePaging) ); const siteEntryModel = { @@ -293,7 +293,7 @@ describe('LibraryMetadataFormComponent', () => { ).and.returnValue( Promise.resolve({ list: { entries: [] } - }) + } as SitePaging) ); const siteEntryModel = { diff --git a/src/app/components/preview/preview.component.spec.ts b/src/app/components/preview/preview.component.spec.ts index bdda3fc0c..852856cdf 100644 --- a/src/app/components/preview/preview.component.spec.ts +++ b/src/app/components/preview/preview.component.spec.ts @@ -48,6 +48,14 @@ import { AppTestingModule } from '../../testing/app-testing.module'; import { ContentApiService } from '@alfresco/aca-shared'; import { ContentManagementService } from '../../services/content-management.service'; import { Store } from '@ngrx/store'; +import { + Node, + NodePaging, + FavoritePaging, + SharedLinkPaging, + PersonEntry, + ResultSetPaging +} from '@alfresco/js-api'; describe('PreviewComponent', () => { let fixture: ComponentFixture; @@ -378,7 +386,7 @@ describe('PreviewComponent', () => { it('should navigate to original location if node not found', async () => { spyOn(router, 'navigate').and.stub(); - spyOn(contentApi, 'getNodeInfo').and.returnValue(Promise.reject('error')); + spyOn(contentApi, 'getNodeInfo').and.returnValue(throwError('error')); component.previewLocation = 'personal-files'; await component.displayNode('folder1'); @@ -392,7 +400,7 @@ describe('PreviewComponent', () => { spyOn(contentApi, 'getNodeInfo').and.returnValue( of({ isFile: false - }) + } as Node) ); component.previewLocation = 'personal-files'; @@ -419,7 +427,7 @@ describe('PreviewComponent', () => { spyOn(contentApi, 'getNodeInfo').and.returnValue( of({ isFile: true - }) + } as Node) ); spyOn(component, 'getNearestNodes').and.returnValue( Promise.reject('error') @@ -434,16 +442,18 @@ describe('PreviewComponent', () => { xit('should setup node for displaying', async () => { spyOn(router, 'navigate').and.stub(); - spyOn(component, 'getNearestNodes').and.returnValue({ - left: 'node1', - right: 'node3' - }); + spyOn(component, 'getNearestNodes').and.returnValue( + Promise.resolve({ + left: 'node1', + right: 'node3' + }) + ); spyOn(contentApi, 'getNodeInfo').and.returnValue( of({ id: 'node2', parentId: 'parent1', isFile: true - }) + } as Node) ); await component.displayNode('node2'); @@ -466,7 +476,7 @@ describe('PreviewComponent', () => { { entry: { id: 'node2', name: 'node 2' } } ] } - }) + } as NodePaging) ); const ids = await component.getFileIds('personal-files', 'folder1'); @@ -485,7 +495,7 @@ describe('PreviewComponent', () => { { entry: { id: 'node2', name: 'node 2' } } ] } - }) + } as NodePaging) ); const ids = await component.getFileIds('personal-files', 'folder1'); @@ -504,11 +514,11 @@ describe('PreviewComponent', () => { of({ list: { entries: [ - { entry: { id: 'node1', name: 'node 1', modifiedAt: 1 } }, - { entry: { id: 'node2', name: 'node 2', modifiedAt: 2 } } + { entry: { id: 'node1', name: 'node 1', modifiedAt: new Date(1) } }, + { entry: { id: 'node2', name: 'node 2', modifiedAt: new Date(2) } } ] } - }) + } as NodePaging) ); const ids = await component.getFileIds('personal-files', 'folder1'); @@ -527,7 +537,7 @@ describe('PreviewComponent', () => { { entry: { id: 'node2', name: 'node 2' } } ] } - }) + } as NodePaging) ); const ids = await component.getFileIds('libraries', 'site1'); @@ -550,7 +560,7 @@ describe('PreviewComponent', () => { { entry: { id: 'node2', name: 'node 2', modifiedAt: new Date(2) } } ] } - }) + } as NodePaging) ); const ids = await component.getFileIds('libraries', 'folder1'); @@ -570,7 +580,7 @@ describe('PreviewComponent', () => { { entry: { target: { file: { id: 'file2', name: 'file 2' } } } } ] } - }) + } as FavoritePaging) ); const ids = await component.getFileIds('favorites'); @@ -601,7 +611,7 @@ describe('PreviewComponent', () => { } ] } - }) + } as FavoritePaging) ); const ids = await component.getFileIds('favorites'); @@ -632,7 +642,7 @@ describe('PreviewComponent', () => { } ] } - }) + } as SharedLinkPaging) ); const ids = await component.getFileIds('shared'); @@ -662,7 +672,7 @@ describe('PreviewComponent', () => { } ] } - }) + } as SharedLinkPaging) ); const ids = await component.getFileIds('shared'); @@ -676,7 +686,7 @@ describe('PreviewComponent', () => { spyOn(contentApi, 'getPerson').and.returnValue( of({ entry: { id: 'user' } - }) + } as PersonEntry) ); spyOn(contentApi, 'search').and.returnValue( @@ -687,7 +697,7 @@ describe('PreviewComponent', () => { { entry: { id: 'node1', name: 'node 1', modifiedAt: new Date(1) } } ] } - }) + } as ResultSetPaging) ); const ids = await component.getFileIds('recent-files'); @@ -700,7 +710,7 @@ describe('PreviewComponent', () => { spyOn(contentApi, 'getPerson').and.returnValue( of({ entry: { id: 'user' } - }) + } as PersonEntry) ); spyOn(contentApi, 'search').and.returnValue( @@ -711,7 +721,7 @@ describe('PreviewComponent', () => { { entry: { id: 'node1', name: 'node 1', modifiedAt: new Date(1) } } ] } - }) + } as ResultSetPaging) ); const ids = await component.getFileIds('recent-files'); diff --git a/src/app/components/recent-files/recent-files.component.spec.ts b/src/app/components/recent-files/recent-files.component.spec.ts index 5b1af8898..36bca8ae5 100644 --- a/src/app/components/recent-files/recent-files.component.spec.ts +++ b/src/app/components/recent-files/recent-files.component.spec.ts @@ -41,6 +41,7 @@ import { DocumentListComponent } from '@alfresco/adf-content-services'; import { RecentFilesComponent } from './recent-files.component'; import { AppTestingModule } from '../../testing/app-testing.module'; import { Router } from '@angular/router'; +import { PersonEntry } from '@alfresco/js-api'; describe('RecentFilesComponent', () => { let fixture: ComponentFixture; @@ -90,7 +91,7 @@ describe('RecentFilesComponent', () => { spyOn(alfrescoApi.peopleApi, 'getPerson').and.returnValue( Promise.resolve({ entry: { id: 'personId' } - }) + } as PersonEntry) ); spyOn(alfrescoApi.searchApi, 'search').and.returnValue( diff --git a/src/app/components/search/search-libraries-results/search-libraries-query-builder.service.spec.ts b/src/app/components/search/search-libraries-results/search-libraries-query-builder.service.spec.ts index 7fa15a839..65976faaa 100644 --- a/src/app/components/search/search-libraries-results/search-libraries-query-builder.service.spec.ts +++ b/src/app/components/search/search-libraries-results/search-libraries-query-builder.service.spec.ts @@ -26,12 +26,16 @@ import { TestBed } from '@angular/core/testing'; import { AppTestingModule } from '../../../testing/app-testing.module'; import { AlfrescoApiService } from '@alfresco/adf-core'; -import { SearchLibrariesQueryBuilderService } from './search-libraries-query-builder.service'; +import { + SearchLibrariesQueryBuilderService, + LibrarySearchQuery +} from './search-libraries-query-builder.service'; describe('SearchLibrariesQueryBuilderService', () => { let apiService: AlfrescoApiService; let builder: SearchLibrariesQueryBuilderService; let queriesApi; + const query = {} as LibrarySearchQuery; beforeEach(() => { TestBed.configureTestingModule({ @@ -54,7 +58,6 @@ describe('SearchLibrariesQueryBuilderService', () => { }); it('should build query and raise an event on update', async () => { - const query = {}; spyOn(builder, 'buildQuery').and.returnValue(query); let eventArgs = null; @@ -68,7 +71,6 @@ describe('SearchLibrariesQueryBuilderService', () => { const data = {}; spyOn(queriesApi, 'findSites').and.returnValue(Promise.resolve(data)); - const query = {}; spyOn(builder, 'buildQuery').and.returnValue(query); let eventArgs = null; @@ -106,7 +108,6 @@ describe('SearchLibrariesQueryBuilderService', () => { const err = '{"error": {"statusCode": 400}}'; spyOn(queriesApi, 'findSites').and.returnValue(Promise.reject(err)); - const query = {}; spyOn(builder, 'buildQuery').and.returnValue(query); let eventArgs = null; diff --git a/src/app/components/search/search-results/search-results.component.spec.ts b/src/app/components/search/search-results/search-results.component.spec.ts index 43f653602..2dcde32c8 100644 --- a/src/app/components/search/search-results/search-results.component.spec.ts +++ b/src/app/components/search/search-results/search-results.component.spec.ts @@ -45,7 +45,7 @@ import { NavigateToFolder, SnackbarErrorAction } from '@alfresco/aca-shared/store'; -import { Pagination } from '@alfresco/js-api'; +import { Pagination, SearchRequest } from '@alfresco/js-api'; import { SearchQueryBuilderService } from '@alfresco/adf-content-services'; import { ActivatedRoute, Router } from '@angular/router'; @@ -58,6 +58,7 @@ describe('SearchComponent', () => { let alfrescoApi: AlfrescoApiService; let translate: TranslationService; let router: Router; + const searchRequest = {} as SearchRequest; beforeEach(() => { TestBed.configureTestingModule({ @@ -108,7 +109,7 @@ describe('SearchComponent', () => { }) ); - spyOn(queryBuilder, 'buildQuery').and.returnValue({}); + spyOn(queryBuilder, 'buildQuery').and.returnValue(searchRequest); spyOn(store, 'dispatch').and.stub(); queryBuilder.execute(); @@ -133,7 +134,7 @@ describe('SearchComponent', () => { }) ); - spyOn(queryBuilder, 'buildQuery').and.returnValue({}); + spyOn(queryBuilder, 'buildQuery').and.returnValue(searchRequest); spyOn(store, 'dispatch').and.stub(); queryBuilder.execute(); @@ -158,7 +159,7 @@ describe('SearchComponent', () => { }) ); - spyOn(queryBuilder, 'buildQuery').and.returnValue({}); + spyOn(queryBuilder, 'buildQuery').and.returnValue(searchRequest); spyOn(store, 'dispatch').and.stub(); queryBuilder.execute(); diff --git a/src/app/extensions/extension.service.spec.ts b/src/app/extensions/extension.service.spec.ts index 514e52f70..28b5f30bc 100644 --- a/src/app/extensions/extension.service.spec.ts +++ b/src/app/extensions/extension.service.spec.ts @@ -26,7 +26,7 @@ import { TestBed } from '@angular/core/testing'; import { AppTestingModule } from '../testing/app-testing.module'; import { AppExtensionService } from './extension.service'; -import { Store } from '@ngrx/store'; +import { Store, Action } from '@ngrx/store'; import { AppStore } from '@alfresco/aca-shared/store'; import { ContentActionType, @@ -37,7 +37,8 @@ import { reduceEmptyMenus, ExtensionService, ExtensionConfig, - ComponentRegisterService + ComponentRegisterService, + NavBarGroupRef } from '@alfresco/adf-extensions'; import { AppConfigService } from '@alfresco/adf-core'; @@ -155,7 +156,7 @@ describe('AppExtensionService', () => { expect(store.dispatch).toHaveBeenCalledWith({ type: 'CREATE_FOLDER', payload: 'folder-name' - }); + } as Action); }); it('should still invoke store if action is missing', () => { @@ -667,6 +668,13 @@ describe('AppExtensionService', () => { }); describe('getApplicationNavigation', () => { + beforeEach(() => { + extensions.setEvaluators({ + notVisible: () => false, + isVisible: () => true + }); + }); + it('should create navigation data', () => { const navigation = service.getApplicationNavigation([ { items: [{ route: 'route1' }, { route: 'route2' }] }, @@ -683,7 +691,7 @@ describe('AppExtensionService', () => { { items: [{ children: [{ route: 'route3', url: '/route3' }] }] } - ]); + ] as NavBarGroupRef[]); }); it('should filter out disabled items', () => { @@ -695,18 +703,10 @@ describe('AppExtensionService', () => { expect(navigation).toEqual([ { items: [{ route: 'route1', url: '/route1' }] }, { items: [{ children: [] }] } - ]); + ] as NavBarGroupRef[]); }); it('should filter out items based on rule', () => { - spyOn(service, 'filterVisible').and.callFake(item => { - if (item.rules) { - return item.rules.visible; - } else { - return true; - } - }); - const navigation = service.getApplicationNavigation([ { id: 'groupId', @@ -714,7 +714,7 @@ describe('AppExtensionService', () => { { id: 'itemId', route: 'route1', - rules: { visible: false } + rules: { visible: 'notVisible' } } ] } @@ -724,19 +724,11 @@ describe('AppExtensionService', () => { }); it('should filter out group based on rule', () => { - spyOn(service, 'filterVisible').and.callFake(item => { - if (item.rules) { - return item.rules.visible; - } else { - return true; - } - }); - const navigation = service.getApplicationNavigation([ { id: 'group1', rules: { - visible: false + visible: 'notVisible' }, items: [ { diff --git a/src/app/extensions/extension.service.ts b/src/app/extensions/extension.service.ts index 07c35dc1e..892d4a639 100644 --- a/src/app/extensions/extension.service.ts +++ b/src/app/extensions/extension.service.ts @@ -267,7 +267,7 @@ export class AppExtensionService implements RuleContext { .filter(entry => !entry.disabled); } - getApplicationNavigation(elements) { + getApplicationNavigation(elements): Array { return elements .filter(group => this.filterVisible(group)) .map(group => { diff --git a/src/app/services/content-management.service.spec.ts b/src/app/services/content-management.service.spec.ts index c29d562ad..4f26878c9 100644 --- a/src/app/services/content-management.service.spec.ts +++ b/src/app/services/content-management.service.spec.ts @@ -52,8 +52,13 @@ import { Store } from '@ngrx/store'; import { ContentManagementService } from './content-management.service'; import { NodeActionsService } from './node-actions.service'; import { TranslationService, AlfrescoApiService } from '@alfresco/adf-core'; -import { MatDialog } from '@angular/material/dialog'; -import { MatSnackBar } from '@angular/material/snack-bar'; +import { MatDialog, MatDialogRef } from '@angular/material/dialog'; +import { + MatSnackBar, + MatSnackBarRef, + SimpleSnackBar +} from '@angular/material/snack-bar'; +import { NodeEntry, Node } from '@alfresco/js-api'; describe('ContentManagementService', () => { let dialog: MatDialog; @@ -84,14 +89,17 @@ describe('ContentManagementService', () => { }); describe('Copy node action', () => { + let subject: Subject; + beforeEach(() => { + subject = new Subject(); spyOn(snackBar, 'open').and.callThrough(); }); + afterEach(() => subject.complete()); + it('notifies successful copy of a node', () => { - spyOn(nodeActions, 'copyNodes').and.returnValue( - of('OPERATION.SUCCES.CONTENT.COPY') - ); + spyOn(nodeActions, 'copyNodes').and.returnValue(subject); const selection: any[] = [ { entry: { id: 'node-to-copy-id', name: 'name' } } @@ -100,6 +108,7 @@ describe('ContentManagementService', () => { store.dispatch(new CopyNodesAction(selection)); nodeActions.contentCopied.next(createdItems); + subject.next('OPERATION.SUCCES.CONTENT.COPY'); expect(nodeActions.copyNodes).toHaveBeenCalled(); expect(snackBar.open['calls'].argsFor(0)[0]).toBe( @@ -108,9 +117,7 @@ describe('ContentManagementService', () => { }); it('notifies successful copy of multiple nodes', () => { - spyOn(nodeActions, 'copyNodes').and.returnValue( - of('OPERATION.SUCCES.CONTENT.COPY') - ); + spyOn(nodeActions, 'copyNodes').and.returnValue(subject); const selection: any[] = [ { entry: { id: 'node-to-copy-1', name: 'name1' } }, @@ -123,6 +130,7 @@ describe('ContentManagementService', () => { store.dispatch(new CopyNodesAction(selection)); nodeActions.contentCopied.next(createdItems); + subject.next('OPERATION.SUCCES.CONTENT.COPY'); expect(nodeActions.copyNodes).toHaveBeenCalled(); expect(snackBar.open['calls'].argsFor(0)[0]).toBe( @@ -131,9 +139,7 @@ describe('ContentManagementService', () => { }); it('notifies partially copy of one node out of a multiple selection of nodes', () => { - spyOn(nodeActions, 'copyNodes').and.returnValue( - of('OPERATION.SUCCES.CONTENT.COPY') - ); + spyOn(nodeActions, 'copyNodes').and.returnValue(subject); const selection: any[] = [ { entry: { id: 'node-to-copy-1', name: 'name1' } }, @@ -145,6 +151,7 @@ describe('ContentManagementService', () => { store.dispatch(new CopyNodesAction(selection)); nodeActions.contentCopied.next(createdItems); + subject.next('OPERATION.SUCCES.CONTENT.COPY'); expect(nodeActions.copyNodes).toHaveBeenCalled(); expect(snackBar.open['calls'].argsFor(0)[0]).toBe( @@ -153,9 +160,7 @@ describe('ContentManagementService', () => { }); it('notifies partially copy of more nodes out of a multiple selection of nodes', () => { - spyOn(nodeActions, 'copyNodes').and.returnValue( - of('OPERATION.SUCCES.CONTENT.COPY') - ); + spyOn(nodeActions, 'copyNodes').and.returnValue(subject); const selection: any[] = [ { entry: { id: 'node-to-copy-0', name: 'name0' } }, @@ -169,6 +174,7 @@ describe('ContentManagementService', () => { store.dispatch(new CopyNodesAction(selection)); nodeActions.contentCopied.next(createdItems); + subject.next('OPERATION.SUCCES.CONTENT.COPY'); expect(nodeActions.copyNodes).toHaveBeenCalled(); expect(snackBar.open['calls'].argsFor(0)[0]).toBe( @@ -177,9 +183,7 @@ describe('ContentManagementService', () => { }); it('notifies of failed copy of multiple nodes', () => { - spyOn(nodeActions, 'copyNodes').and.returnValue( - of('OPERATION.SUCCES.CONTENT.COPY') - ); + spyOn(nodeActions, 'copyNodes').and.returnValue(subject); const selection: any[] = [ { entry: { id: 'node-to-copy-0', name: 'name0' } }, @@ -190,6 +194,7 @@ describe('ContentManagementService', () => { store.dispatch(new CopyNodesAction(selection)); nodeActions.contentCopied.next(createdItems); + subject.next('OPERATION.SUCCES.CONTENT.COPY'); expect(nodeActions.copyNodes).toHaveBeenCalled(); expect(snackBar.open['calls'].argsFor(0)[0]).toBe( @@ -198,9 +203,7 @@ describe('ContentManagementService', () => { }); it('notifies of failed copy of one node', () => { - spyOn(nodeActions, 'copyNodes').and.returnValue( - of('OPERATION.SUCCES.CONTENT.COPY') - ); + spyOn(nodeActions, 'copyNodes').and.returnValue(subject); const selection: any[] = [ { entry: { id: 'node-to-copy', name: 'name' } } @@ -209,6 +212,7 @@ describe('ContentManagementService', () => { store.dispatch(new CopyNodesAction(selection)); nodeActions.contentCopied.next(createdItems); + subject.next('OPERATION.SUCCES.CONTENT.COPY'); expect(nodeActions.copyNodes).toHaveBeenCalled(); expect(snackBar.open['calls'].argsFor(0)[0]).toBe( @@ -217,7 +221,7 @@ describe('ContentManagementService', () => { }); it('notifies error if success message was not emitted', () => { - spyOn(nodeActions, 'copyNodes').and.returnValue(of('')); + spyOn(nodeActions, 'copyNodes').and.returnValue(subject); const selection: any[] = [ { entry: { id: 'node-to-copy-id', name: 'name' } } @@ -225,6 +229,7 @@ describe('ContentManagementService', () => { store.dispatch(new CopyNodesAction(selection)); nodeActions.contentCopied.next(); + subject.next(''); expect(nodeActions.copyNodes).toHaveBeenCalled(); expect(snackBar.open['calls'].argsFor(0)[0]).toBe( @@ -233,12 +238,11 @@ describe('ContentManagementService', () => { }); it('notifies permission error on copy of node', () => { - spyOn(nodeActions, 'copyNodes').and.returnValue( - throwError(new Error(JSON.stringify({ error: { statusCode: 403 } }))) - ); + spyOn(nodeActions, 'copyNodes').and.returnValue(subject); const selection: any[] = [{ entry: { id: '1', name: 'name' } }]; store.dispatch(new CopyNodesAction(selection)); + subject.error(new Error(JSON.stringify({ error: { statusCode: 403 } }))); expect(nodeActions.copyNodes).toHaveBeenCalled(); expect(snackBar.open['calls'].argsFor(0)[0]).toBe( @@ -247,13 +251,12 @@ describe('ContentManagementService', () => { }); it('notifies generic error message on all errors, but 403', () => { - spyOn(nodeActions, 'copyNodes').and.returnValue( - throwError(new Error(JSON.stringify({ error: { statusCode: 404 } }))) - ); + spyOn(nodeActions, 'copyNodes').and.returnValue(subject); const selection: any[] = [{ entry: { id: '1', name: 'name' } }]; store.dispatch(new CopyNodesAction(selection)); + subject.error(new Error(JSON.stringify({ error: { statusCode: 404 } }))); expect(nodeActions.copyNodes).toHaveBeenCalled(); expect(snackBar.open['calls'].argsFor(0)[0]).toBe( @@ -263,14 +266,15 @@ describe('ContentManagementService', () => { }); describe('Undo Copy action', () => { - beforeEach(() => { - spyOn(nodeActions, 'copyNodes').and.returnValue( - of('OPERATION.SUCCES.CONTENT.COPY') - ); + let subject: Subject; + beforeEach(() => { + subject = new Subject(); + + spyOn(nodeActions, 'copyNodes').and.returnValue(subject); spyOn(snackBar, 'open').and.returnValue({ - onAction: () => of({}) - }); + onAction: () => of(null) + } as MatSnackBarRef); }); it('should delete the newly created node on Undo action', () => { @@ -282,6 +286,7 @@ describe('ContentManagementService', () => { const createdItems: any[] = [{ entry: { id: 'copy-id', name: 'name' } }]; store.dispatch(new CopyNodesAction(selection)); + nodeActions.copyNodes(null).next('OPERATION.SUCCES.CONTENT.COPY'); nodeActions.contentCopied.next(createdItems); expect(nodeActions.copyNodes).toHaveBeenCalled(); @@ -325,6 +330,7 @@ describe('ContentManagementService', () => { ]; store.dispatch(new CopyNodesAction(selection)); + nodeActions.copyNodes(null).next('OPERATION.SUCCES.CONTENT.COPY'); nodeActions.contentCopied.next(createdItems); expect(nodeActions.copyNodes).toHaveBeenCalled(); @@ -348,6 +354,7 @@ describe('ContentManagementService', () => { const createdItems: any[] = [{ entry: { id: 'copy-id', name: 'name' } }]; store.dispatch(new CopyNodesAction(selection)); + nodeActions.copyNodes(null).next('OPERATION.SUCCES.CONTENT.COPY'); nodeActions.contentCopied.next(createdItems); expect(nodeActions.copyNodes).toHaveBeenCalled(); @@ -368,6 +375,7 @@ describe('ContentManagementService', () => { const createdItems: any[] = [{ entry: { id: 'copy-id', name: 'name' } }]; store.dispatch(new CopyNodesAction(selection)); + nodeActions.copyNodes(null).next('OPERATION.SUCCES.CONTENT.COPY'); nodeActions.contentCopied.next(createdItems); expect(nodeActions.copyNodes).toHaveBeenCalled(); @@ -388,6 +396,7 @@ describe('ContentManagementService', () => { const createdItems: any[] = [{ entry: { id: 'copy-id', name: 'name' } }]; store.dispatch(new CopyNodesAction(selection)); + nodeActions.copyNodes(null).next('OPERATION.SUCCES.CONTENT.COPY'); nodeActions.contentCopied.next(createdItems); expect(nodeActions.copyNodes).toHaveBeenCalled(); @@ -399,6 +408,8 @@ describe('ContentManagementService', () => { }); describe('Move node action', () => { + let subject: Subject; + beforeEach(() => { spyOn(translationService, 'instant').and.callFake(keysArray => { if (Array.isArray(keysArray)) { @@ -414,9 +425,12 @@ describe('ContentManagementService', () => { }); beforeEach(() => { + subject = new Subject(); spyOn(snackBar, 'open').and.callThrough(); }); + afterEach(() => subject.complete()); + it('notifies successful move of a node', () => { const node = [{ entry: { id: 'node-to-move-id', name: 'name' } }]; const moveResponse = { @@ -425,14 +439,12 @@ describe('ContentManagementService', () => { partiallySucceeded: [] }; - spyOn(nodeActions, 'moveNodes').and.returnValue( - of('OPERATION.SUCCES.CONTENT.MOVE') - ); + spyOn(nodeActions, 'moveNodes').and.returnValue(subject); spyOn(nodeActions, 'processResponse').and.returnValue(moveResponse); const selection: any = node; store.dispatch(new MoveNodesAction(selection)); - + nodeActions.moveNodes(null).next('OPERATION.SUCCES.CONTENT.MOVE'); nodeActions.contentMoved.next(moveResponse); expect(nodeActions.moveNodes).toHaveBeenCalled(); @@ -452,14 +464,13 @@ describe('ContentManagementService', () => { partiallySucceeded: [] }; - spyOn(nodeActions, 'moveNodes').and.returnValue( - of('OPERATION.SUCCES.CONTENT.MOVE') - ); + spyOn(nodeActions, 'moveNodes').and.returnValue(subject); spyOn(nodeActions, 'processResponse').and.returnValue(moveResponse); const selection: any = nodes; store.dispatch(new MoveNodesAction(selection)); + nodeActions.moveNodes(null).next('OPERATION.SUCCES.CONTENT.MOVE'); nodeActions.contentMoved.next(moveResponse); expect(nodeActions.moveNodes).toHaveBeenCalled(); @@ -476,14 +487,13 @@ describe('ContentManagementService', () => { partiallySucceeded: nodes }; - spyOn(nodeActions, 'moveNodes').and.returnValue( - of('OPERATION.SUCCES.CONTENT.MOVE') - ); + spyOn(nodeActions, 'moveNodes').and.returnValue(subject); spyOn(nodeActions, 'processResponse').and.returnValue(moveResponse); const selection = nodes; store.dispatch(new MoveNodesAction(selection)); + nodeActions.moveNodes(null).next('OPERATION.SUCCES.CONTENT.MOVE'); nodeActions.contentMoved.next(moveResponse); expect(nodeActions.moveNodes).toHaveBeenCalled(); @@ -503,14 +513,13 @@ describe('ContentManagementService', () => { partiallySucceeded: nodes }; - spyOn(nodeActions, 'moveNodes').and.returnValue( - of('OPERATION.SUCCES.CONTENT.MOVE') - ); + spyOn(nodeActions, 'moveNodes').and.returnValue(subject); spyOn(nodeActions, 'processResponse').and.returnValue(moveResponse); const selection = nodes; store.dispatch(new MoveNodesAction(selection)); + nodeActions.moveNodes(null).next('OPERATION.SUCCES.CONTENT.MOVE'); nodeActions.contentMoved.next(moveResponse); expect(nodeActions.moveNodes).toHaveBeenCalled(); @@ -530,13 +539,11 @@ describe('ContentManagementService', () => { partiallySucceeded: [] }; - spyOn(nodeActions, 'moveNodes').and.returnValue( - of('OPERATION.SUCCES.CONTENT.MOVE') - ); + spyOn(nodeActions, 'moveNodes').and.returnValue(subject); spyOn(nodeActions, 'processResponse').and.returnValue(moveResponse); store.dispatch(new MoveNodesAction(nodes)); - + nodeActions.moveNodes(null).next('OPERATION.SUCCES.CONTENT.MOVE'); nodeActions.contentMoved.next(moveResponse); expect(nodeActions.moveNodes).toHaveBeenCalled(); @@ -556,12 +563,11 @@ describe('ContentManagementService', () => { partiallySucceeded: [nodes[1]] }; - spyOn(nodeActions, 'moveNodes').and.returnValue( - of('OPERATION.SUCCES.CONTENT.MOVE') - ); + spyOn(nodeActions, 'moveNodes').and.returnValue(subject); spyOn(nodeActions, 'processResponse').and.returnValue(moveResponse); store.dispatch(new MoveNodesAction(nodes)); + nodeActions.moveNodes(null).next('OPERATION.SUCCES.CONTENT.MOVE'); nodeActions.contentMoved.next(moveResponse); expect(nodeActions.moveNodes).toHaveBeenCalled(); @@ -578,9 +584,10 @@ describe('ContentManagementService', () => { partiallySucceeded: [] }; - spyOn(nodeActions, 'moveNodes').and.returnValue(of('')); + spyOn(nodeActions, 'moveNodes').and.returnValue(subject); store.dispatch(new MoveNodesAction(nodes)); + nodeActions.moveNodes(null).next(''); nodeActions.contentMoved.next(moveResponse); expect(nodeActions.moveNodes).toHaveBeenCalled(); @@ -590,12 +597,13 @@ describe('ContentManagementService', () => { }); it('notifies permission error on move of node', () => { - spyOn(nodeActions, 'moveNodes').and.returnValue( - throwError(new Error(JSON.stringify({ error: { statusCode: 403 } }))) - ); + spyOn(nodeActions, 'moveNodes').and.returnValue(subject); const selection: any[] = [{ entry: { id: '1', name: 'name' } }]; store.dispatch(new MoveNodesAction(selection)); + nodeActions + .moveNodes(null) + .error(new Error(JSON.stringify({ error: { statusCode: 403 } }))); expect(nodeActions.moveNodes).toHaveBeenCalled(); expect(snackBar.open['calls'].argsFor(0)[0]).toBe( @@ -604,12 +612,13 @@ describe('ContentManagementService', () => { }); it('notifies generic error message on all errors, but 403', () => { - spyOn(nodeActions, 'moveNodes').and.returnValue( - throwError(new Error(JSON.stringify({ error: { statusCode: 404 } }))) - ); + spyOn(nodeActions, 'moveNodes').and.returnValue(subject); const selection: any[] = [{ entry: { id: '1', name: 'name' } }]; store.dispatch(new MoveNodesAction(selection)); + nodeActions + .moveNodes(null) + .error(new Error(JSON.stringify({ error: { statusCode: 404 } }))); expect(nodeActions.moveNodes).toHaveBeenCalled(); expect(snackBar.open['calls'].argsFor(0)[0]).toBe( @@ -618,12 +627,13 @@ describe('ContentManagementService', () => { }); it('notifies conflict error message on 409', () => { - spyOn(nodeActions, 'moveNodes').and.returnValue( - throwError(new Error(JSON.stringify({ error: { statusCode: 409 } }))) - ); + spyOn(nodeActions, 'moveNodes').and.returnValue(subject); const selection: any[] = [{ entry: { id: '1', name: 'name' } }]; store.dispatch(new MoveNodesAction(selection)); + nodeActions + .moveNodes(null) + .error(new Error(JSON.stringify({ error: { statusCode: 409 } }))); expect(nodeActions.moveNodes).toHaveBeenCalled(); expect(snackBar.open['calls'].argsFor(0)[0]).toBe( @@ -639,12 +649,11 @@ describe('ContentManagementService', () => { partiallySucceeded: [] }; - spyOn(nodeActions, 'moveNodes').and.returnValue( - of('OPERATION.SUCCES.CONTENT.MOVE') - ); + spyOn(nodeActions, 'moveNodes').and.returnValue(subject); spyOn(nodeActions, 'processResponse').and.returnValue(moveResponse); store.dispatch(new MoveNodesAction(nodes)); + nodeActions.moveNodes(null).next('OPERATION.SUCCES.CONTENT.MOVE'); nodeActions.contentMoved.next(moveResponse); expect(nodeActions.moveNodes).toHaveBeenCalled(); @@ -655,6 +664,8 @@ describe('ContentManagementService', () => { }); describe('Undo Move action', () => { + let subject: Subject; + beforeEach(() => { spyOn(translationService, 'instant').and.callFake(keysArray => { if (Array.isArray(keysArray)) { @@ -670,15 +681,16 @@ describe('ContentManagementService', () => { }); beforeEach(() => { - spyOn(nodeActions, 'moveNodes').and.returnValue( - of('OPERATION.SUCCES.CONTENT.MOVE') - ); + subject = new Subject(); + spyOn(nodeActions, 'moveNodes').and.returnValue(subject); spyOn(snackBar, 'open').and.returnValue({ - onAction: () => of({}) - }); + onAction: () => of(null) + } as MatSnackBarRef); }); + afterEach(() => subject.next()); + it('should move node back to initial parent, after succeeded move', () => { const initialParent = 'parent-id-0'; const node = { @@ -689,6 +701,7 @@ describe('ContentManagementService', () => { spyOn(nodeActions, 'moveNodeAction').and.returnValue(of({})); store.dispatch(new MoveNodesAction(selection)); + nodeActions.moveNodes(null).next('OPERATION.SUCCES.CONTENT.MOVE'); const movedItems = { failed: [], partiallySucceeded: [], @@ -726,6 +739,7 @@ describe('ContentManagementService', () => { }; store.dispatch(new MoveNodesAction(selection)); + nodeActions.moveNodes(null).next('OPERATION.SUCCES.CONTENT.MOVE'); nodeActions.contentMoved.next(movedItems); expect(nodeActions.moveNodeAction).toHaveBeenCalledWith( @@ -762,6 +776,7 @@ describe('ContentManagementService', () => { }; store.dispatch(new MoveNodesAction(selection)); + nodeActions.moveNodes(null).next('OPERATION.SUCCES.CONTENT.MOVE'); nodeActions.contentMoved.next(movedItems); expect(contentApi.restoreNode).toHaveBeenCalled(); @@ -805,6 +820,7 @@ describe('ContentManagementService', () => { }; store.dispatch(new MoveNodesAction(selection)); + nodeActions.moveNodes(null).next('OPERATION.SUCCES.CONTENT.MOVE'); nodeActions.contentMoved.next(movedItems); expect(contentApi.restoreNode).toHaveBeenCalled(); @@ -838,6 +854,7 @@ describe('ContentManagementService', () => { }; store.dispatch(new MoveNodesAction(selection)); + nodeActions.moveNodes(null).next('OPERATION.SUCCES.CONTENT.MOVE'); nodeActions.contentMoved.next(movedItems); expect(contentApi.restoreNode).toHaveBeenCalled(); @@ -871,6 +888,7 @@ describe('ContentManagementService', () => { }; store.dispatch(new MoveNodesAction(selection)); + nodeActions.moveNodes(null).next('OPERATION.SUCCES.CONTENT.MOVE'); nodeActions.contentMoved.next(movedItems); expect(nodeActions.moveNodes).toHaveBeenCalled(); @@ -999,7 +1017,7 @@ describe('ContentManagementService', () => { afterClosed() { return of(true); } - }); + } as MatDialogRef); }); it('does not purge nodes if no selection', () => { @@ -1185,7 +1203,7 @@ describe('ContentManagementService', () => { }); it('call restore nodes if selection has nodes with path', fakeAsync(() => { - spyOn(contentApi, 'restoreNode').and.returnValue(of({})); + spyOn(contentApi, 'restoreNode').and.returnValue(of({} as NodeEntry)); spyOn(contentApi, 'getDeletedNodes').and.returnValue( of({ list: { entries: [] } @@ -1217,7 +1235,7 @@ describe('ContentManagementService', () => { it('should navigate to library folder when node is a library content', fakeAsync(() => { spyOn(store, 'dispatch').and.callThrough(); - spyOn(contentApi, 'restoreNode').and.returnValue(of({})); + spyOn(contentApi, 'restoreNode').and.returnValue(of({} as NodeEntry)); spyOn(contentApi, 'getDeletedNodes').and.returnValue( of({ list: { entries: [] } @@ -1273,7 +1291,7 @@ describe('ContentManagementService', () => { spyOn(contentApi, 'restoreNode').and.callFake(id => { if (id === '1') { - return of({}); + return of({} as NodeEntry); } if (id === '2') { @@ -1284,7 +1302,7 @@ describe('ContentManagementService', () => { return throwError(error); } - return of({}); + return of({} as NodeEntry); }); const path = { @@ -1378,15 +1396,16 @@ describe('ContentManagementService', () => { it('should raise info message when restore multiple nodes', fakeAsync(done => { spyOn(contentApi, 'restoreNode').and.callFake(id => { + const entry = {} as NodeEntry; if (id === '1') { - return of({}); + return of(entry); } if (id === '2') { - return of({}); + return of(entry); } - return of({}); + return of(entry); }); actions$.pipe( @@ -1411,8 +1430,8 @@ describe('ContentManagementService', () => { store.dispatch(new RestoreDeletedNodesAction(selection)); })); - xit('should raise info message when restore selected node', fakeAsync(done => { - spyOn(contentApi, 'restoreNode').and.returnValue(of({})); + it('should raise info message when restore selected node', fakeAsync(done => { + spyOn(contentApi, 'restoreNode').and.returnValue(of({} as NodeEntry)); actions$.pipe( ofType(SnackbarActionTypes.Info), @@ -1434,7 +1453,7 @@ describe('ContentManagementService', () => { })); it('navigate to restore selected node location onAction', fakeAsync(done => { - spyOn(contentApi, 'restoreNode').and.returnValue(of({})); + spyOn(contentApi, 'restoreNode').and.returnValue(of({} as NodeEntry)); actions$.pipe( ofType(RouterActionTypes.NavigateRoute), @@ -1467,13 +1486,13 @@ describe('ContentManagementService', () => { describe('Share Node', () => { it('should open dialog for nodes without requesting getNodeInfo', fakeAsync(() => { - const node: any = { entry: { id: '1', name: 'name1' } }; - spyOn(contentApi, 'getNodeInfo').and.returnValue(of({})); + const node = { entry: { id: '1', name: 'name1' } } as any; + spyOn(contentApi, 'getNodeInfo').and.returnValue(of({} as Node)); spyOn(dialog, 'open').and.returnValue({ afterClosed() { return of(null); } - }); + } as MatDialogRef); store.dispatch(new ShareNodeAction(node)); @@ -1482,13 +1501,15 @@ describe('ContentManagementService', () => { })); it('should open dialog with getNodeInfo data when `id` property is missing', fakeAsync(() => { - const node: any = { entry: { nodeId: '1', name: 'name1' } }; - spyOn(contentApi, 'getNodeInfo').and.returnValue(of({})); + const node = { + entry: { nodeId: '1', name: 'name1' } + } as any; + spyOn(contentApi, 'getNodeInfo').and.returnValue(of({} as Node)); spyOn(dialog, 'open').and.returnValue({ afterClosed() { return of(null); } - }); + } as MatDialogRef); store.dispatch(new ShareNodeAction(node)); @@ -1497,13 +1518,13 @@ describe('ContentManagementService', () => { })); it('should update node selection after dialog is closed', fakeAsync(() => { - const node: any = { entry: { id: '1', name: 'name1' } }; + const node = { entry: { id: '1', name: 'name1' } } as NodeEntry; spyOn(store, 'dispatch').and.callThrough(); spyOn(dialog, 'open').and.returnValue({ afterClosed() { return of(null); } - }); + } as MatDialogRef); store.dispatch(new ShareNodeAction(node)); @@ -1513,11 +1534,11 @@ describe('ContentManagementService', () => { })); it('should emit event when node is un-shared', fakeAsync(() => { - const node: any = { entry: { id: '1', name: 'name1' } }; + const node = { entry: { id: '1', name: 'name1' } } as NodeEntry; spyOn(contentManagementService.linksUnshared, 'next').and.callThrough(); spyOn(dialog, 'open').and.returnValue({ afterClosed: () => of(node) - }); + } as MatDialogRef); store.dispatch(new ShareNodeAction(node)); tick(); @@ -1531,7 +1552,9 @@ describe('ContentManagementService', () => { describe('Unlock Node', () => { it('should unlock node', fakeAsync(() => { - spyOn(contentApi, 'unlockNode').and.returnValue(Promise.resolve({})); + spyOn(contentApi, 'unlockNode').and.returnValue( + Promise.resolve({} as NodeEntry) + ); store.dispatch(new UnlockWriteAction({ entry: { id: 'node-id' } })); tick(); diff --git a/src/app/services/content-management.service.ts b/src/app/services/content-management.service.ts index ea830cf13..195a0a664 100644 --- a/src/app/services/content-management.service.ts +++ b/src/app/services/content-management.service.ts @@ -1164,7 +1164,7 @@ export class ContentManagementService { return i18nMessageString; } - getNodeInfo() { + getNodeInfo(): Observable { return this.store.select(getAppSelection).pipe( take(1), flatMap(({ file }) => { @@ -1178,8 +1178,8 @@ export class ContentManagementService { ); } - unlockNode(node: NodeEntry) { - this.contentApi.unlockNode(node.entry.id).catch(() => { + unlockNode(node: NodeEntry): Promise { + return this.contentApi.unlockNode(node.entry.id).catch(() => { this.store.dispatch( new SnackbarErrorAction('APP.MESSAGES.ERRORS.UNLOCK_NODE', { fileName: node.entry.name diff --git a/src/app/services/node-actions.service.spec.ts b/src/app/services/node-actions.service.spec.ts index abdadec1c..9c851481f 100644 --- a/src/app/services/node-actions.service.spec.ts +++ b/src/app/services/node-actions.service.spec.ts @@ -24,12 +24,16 @@ */ import { TestBed, async } from '@angular/core/testing'; -import { MatDialog } from '@angular/material/dialog'; -import { of, throwError } from 'rxjs'; +import { MatDialog, MatDialogRef } from '@angular/material/dialog'; +import { of, throwError, Subject } from 'rxjs'; import { AlfrescoApiService, TranslationService } from '@alfresco/adf-core'; import { DocumentListService } from '@alfresco/adf-content-services'; -import { NodeActionsService } from './node-actions.service'; -import { MinimalNodeEntryEntity } from '@alfresco/js-api'; +import { NodeActionsService, BatchOperationType } from './node-actions.service'; +import { + MinimalNodeEntryEntity, + NodeChildAssociationEntry, + NodeEntry +} from '@alfresco/js-api'; import { AppTestingModule } from '../testing/app-testing.module'; import { ContentApiService } from '@alfresco/aca-shared'; @@ -80,6 +84,7 @@ describe('NodeActionsService', () => { const spyOnSuccess = jasmine.createSpy('spyOnSuccess'); const spyOnError = jasmine.createSpy('spyOnError'); let contentApi: ContentApiService; + let dialog: MatDialog; const helper = { fakeCopyNode: ( @@ -132,11 +137,100 @@ describe('NodeActionsService', () => { service = TestBed.get(NodeActionsService); apiService = TestBed.get(AlfrescoApiService); + dialog = TestBed.get(MatDialog); apiService.reset(); nodesApi = apiService.getInstance().nodes; }); + describe('ContentNodeSelector configuration', () => { + it('should validate selection when allowableOperation has `create`', async(() => { + spyOn(dialog, 'open'); + const contentEntities = [new TestNode(), { entry: { nodeId: '1234' } }]; + const subject = new Subject(); + + service.getContentNodeSelection('', contentEntities as NodeEntry[]); + subject.next([new TestNode().entry]); + + const isSelectionValid = dialog.open['calls'] + .argsFor(0)[1] + .data.isSelectionValid({ + name: 'some-folder-template', + isFile: false, + isFolder: true, + path: { elements: [{}, {}] }, + allowableOperations: ['create'] + }); + + expect(isSelectionValid).toBe(true); + })); + + it('should invalidate selection when allowableOperation does not have `create`', async(() => { + spyOn(dialog, 'open'); + const contentEntities = [new TestNode(), { entry: { nodeId: '1234' } }]; + const subject = new Subject(); + + service.getContentNodeSelection('', contentEntities as NodeEntry[]); + subject.next([new TestNode().entry]); + + const isSelectionValid = dialog.open['calls'] + .argsFor(0)[1] + .data.isSelectionValid({ + name: 'some-folder-template', + isFile: false, + isFolder: true, + path: { elements: [{}, {}] }, + allowableOperations: ['any'] + }); + + expect(isSelectionValid).toBe(false); + })); + + it('should invalidate selection if isSite', async(() => { + spyOn(dialog, 'open'); + const contentEntities = [new TestNode(), { entry: { nodeId: '1234' } }]; + const subject = new Subject(); + + service.getContentNodeSelection('', contentEntities as NodeEntry[]); + subject.next([new TestNode().entry]); + + const isSelectionValid = dialog.open['calls'] + .argsFor(0)[1] + .data.isSelectionValid({ + name: 'some-folder-template', + isFile: false, + isFolder: true, + path: { elements: [{}, {}] }, + nodeType: 'st:site', + allowableOperations: ['create'] + }); + + expect(isSelectionValid).toBe(false); + })); + + it('should validate selection if not a Site', async(() => { + spyOn(dialog, 'open'); + const contentEntities = [new TestNode(), { entry: { nodeId: '1234' } }]; + const subject = new Subject(); + + service.getContentNodeSelection('', contentEntities as NodeEntry[]); + subject.next([new TestNode().entry]); + + const isSelectionValid = dialog.open['calls'] + .argsFor(0)[1] + .data.isSelectionValid({ + name: 'some-folder-template', + isFile: false, + isFolder: true, + path: { elements: [{}, {}] }, + nodeType: 'cm:folder', + allowableOperations: ['create'] + }); + + expect(isSelectionValid).toBe(true); + })); + }); + describe('doBatchOperation', () => { beforeEach(() => { spyOnSuccess.calls.reset(); @@ -211,30 +305,16 @@ describe('NodeActionsService', () => { it("should not throw error if entry in 'contentEntities' does not have id, but has nodeId property", async(() => { const contentEntities = [new TestNode(), { entry: { nodeId: '1234' } }]; + const subject = new Subject(); - spyOn(service, 'getContentNodeSelection').and.returnValue( - of([new TestNode().entry]) - ); + spyOn(service, 'getContentNodeSelection').and.returnValue(subject); spyOn(service, 'copyNodeAction').and.returnValue(of({})); - const doCopyBatchOperation = service - .copyNodes(contentEntities) - .asObservable(); + service.copyNodes(contentEntities).subscribe(spyOnSuccess, spyOnError); + subject.next([new TestNode().entry]); - doCopyBatchOperation - .toPromise() - .then( - () => { - spyOnSuccess(); - }, - error => { - spyOnError(error); - } - ) - .then(() => { - expect(spyOnSuccess).toHaveBeenCalled(); - expect(spyOnError).not.toHaveBeenCalledWith(badRequestError); - }); + expect(spyOnSuccess).toHaveBeenCalled(); + expect(spyOnError).not.toHaveBeenCalledWith(badRequestError); })); }); @@ -268,11 +348,10 @@ describe('NodeActionsService', () => { spyOn(service, 'getEntryParentId').and.returnValue('parent-id'); - const dialog = TestBed.get(MatDialog); spyOn(dialog, 'open').and.callFake( (_contentNodeSelectorComponent: any, data: any) => { testContentNodeSelectorComponentData = data; - return { componentInstance: {} }; + return { componentInstance: {} } as MatDialogRef; } ); @@ -329,19 +408,20 @@ describe('NodeActionsService', () => { }); it('should be called', () => { + const subject = new Subject(); const spyOnBatchOperation = spyOn( service, 'doBatchOperation' ).and.callThrough(); - spyOn(service, 'getContentNodeSelection').and.returnValue( - of([destinationFolder.entry]) - ); + spyOn(service, 'getContentNodeSelection').and.returnValue(subject); spyOn(service, 'copyNodeAction').and.returnValue(of({})); service.copyNodes([fileToCopy, folderToCopy]); + subject.next([destinationFolder.entry]); + expect(spyOnBatchOperation.calls.count()).toEqual(1); expect(spyOnBatchOperation).toHaveBeenCalledWith( - 'copy', + BatchOperationType.copy, [fileToCopy, folderToCopy], undefined ); @@ -359,18 +439,17 @@ describe('NodeActionsService', () => { spyOn(service, 'getEntryParentId').and.returnValue('parent-id'); let testContentNodeSelectorComponentData; - const dialog = TestBed.get(MatDialog); const spyOnDialog = spyOn(dialog, 'open').and.callFake( (_contentNodeSelectorComponent: any, data: any) => { testContentNodeSelectorComponentData = data; - return { componentInstance: {} }; + return { componentInstance: {} } as MatDialogRef; } ); service.copyNodes([fileToCopy, folderToCopy]); expect(spyOnBatchOperation).toHaveBeenCalledWith( - 'copy', + BatchOperationType.copy, [fileToCopy, folderToCopy], undefined ); @@ -413,11 +492,10 @@ describe('NodeActionsService', () => { spyOn(service, 'getEntryParentId').and.returnValue('parent-id'); let testContentNodeSelectorComponentData; - const dialog = TestBed.get(MatDialog); spyOn(dialog, 'open').and.callFake( (_contentNodeSelectorComponent: any, data: any) => { testContentNodeSelectorComponentData = data; - return { componentInstance: {} }; + return { componentInstance: {} } as MatDialogRef; } ); @@ -443,11 +521,10 @@ describe('NodeActionsService', () => { spyOn(service, 'getEntryParentId').and.returnValue('parent-id'); let testContentNodeSelectorComponentData; - const dialog = TestBed.get(MatDialog); spyOn(dialog, 'open').and.callFake( (_contentNodeSelectorComponent: any, data: any) => { testContentNodeSelectorComponentData = data; - return { componentInstance: {} }; + return { componentInstance: {} } as MatDialogRef; } ); @@ -636,8 +713,10 @@ describe('NodeActionsService', () => { let spyOnContentAction; let spyOnFolderAction; let copyObservable; + let subject: Subject; beforeEach(() => { + subject = new Subject(); folderToCopy = new TestNode( 'folder-to-copy-id', !isFile, @@ -673,6 +752,8 @@ describe('NodeActionsService', () => { spyOnError.calls.reset(); }); + afterEach(() => subject.complete()); + it('when folder to copy has a file as content', async(() => { const testFamilyNodes = [ { @@ -687,7 +768,9 @@ describe('NodeActionsService', () => { spyOn(nodesApi, 'getNodeChildren').and.callFake( helper.fakeGetNodeChildren(testFamilyNodes) ); - spyOn(service, 'getChildByName').and.returnValue(of(existingFolder)); + spyOn(service, 'getChildByName').and.returnValue(of( + existingFolder + ) as any); copyObservable .toPromise() @@ -738,7 +821,7 @@ describe('NodeActionsService', () => { spyOn(nodesApi, 'getNodeChildren').and.callFake( helper.fakeGetNodeChildren(testFamilyNodes) ); - spyOn(service, 'getChildByName').and.returnValue(of(existingFolder)); + spyOn(service, 'getChildByName').and.returnValue(subject); copyObservable .toPromise() @@ -766,6 +849,8 @@ describe('NodeActionsService', () => { ] ]); }); + + subject.next(existingFolder); })); it('when folder to copy has another folder as child', async(() => { @@ -783,7 +868,9 @@ describe('NodeActionsService', () => { spyOn(nodesApi, 'getNodeChildren').and.callFake( helper.fakeGetNodeChildren(testFamilyNodes) ); - spyOn(service, 'getChildByName').and.returnValue(of(existingFolder)); + spyOn(service, 'getChildByName').and.returnValue(of( + existingFolder + ) as any); copyObservable .toPromise() @@ -830,8 +917,10 @@ describe('NodeActionsService', () => { let spyOnBatchOperation; let spyOnDocumentListServiceAction; let documentListService; + let subject: Subject; beforeEach(() => { + subject = new Subject(); fileToMove = new TestNode('file-to-be-moved', isFile, 'file-name'); folderToMove = new TestNode('fid', !isFile, 'folder-name'); destinationFolder = new TestNode(folderDestinationId); @@ -843,11 +932,13 @@ describe('NodeActionsService', () => { ).and.callThrough(); }); + afterEach(() => subject.complete()); + it('should allow to select destination for nodes that have permission to be moved', () => { const spyOnDestinationPicker = spyOn( service, 'getContentNodeSelection' - ).and.returnValue(of([destinationFolder.entry])); + ).and.returnValue(subject); spyOn(service, 'moveContentAction').and.returnValue(of({})); spyOn(service, 'moveFolderAction').and.returnValue(of({})); @@ -855,8 +946,10 @@ describe('NodeActionsService', () => { folderToMove.entry['allowableOperations'] = [permissionToMove]; service.moveNodes([fileToMove, folderToMove], permissionToMove); + subject.next([destinationFolder.entry]); + expect(spyOnBatchOperation).toHaveBeenCalledWith( - 'move', + BatchOperationType.move, [fileToMove, folderToMove], permissionToMove ); @@ -867,14 +960,16 @@ describe('NodeActionsService', () => { const spyOnDestinationPicker = spyOn( service, 'getContentNodeSelection' - ).and.returnValue(of([destinationFolder.entry])); + ).and.returnValue(subject); fileToMove.entry['allowableOperations'] = []; folderToMove.entry['allowableOperations'] = []; service.moveNodes([fileToMove, folderToMove], permissionToMove); + subject.next([destinationFolder.entry]); + expect(spyOnBatchOperation).toHaveBeenCalledWith( - 'move', + BatchOperationType.move, [fileToMove, folderToMove], permissionToMove ); @@ -882,9 +977,7 @@ describe('NodeActionsService', () => { }); it('should call the documentListService moveNode directly for moving a file that has permission to be moved', () => { - spyOn(service, 'getContentNodeSelection').and.returnValue( - of([destinationFolder.entry]) - ); + spyOn(service, 'getContentNodeSelection').and.returnValue(subject); fileToMove.entry['allowableOperations'] = [permissionToMove]; spyOnDocumentListServiceAction = spyOn( documentListService, @@ -893,6 +986,8 @@ describe('NodeActionsService', () => { spyOn(service, 'moveNodeAction'); service.moveNodes([fileToMove], permissionToMove); + subject.next([destinationFolder.entry]); + expect(service.moveNodeAction).not.toHaveBeenCalled(); expect(spyOnDocumentListServiceAction).toHaveBeenCalled(); }); @@ -995,11 +1090,15 @@ describe('NodeActionsService', () => { }); describe('moveFolderAction', () => { + let subject$: Subject; beforeEach(() => { + subject$ = new Subject(); spyOnSuccess.calls.reset(); spyOnError.calls.reset(); }); + afterEach(() => subject$.complete()); + it('should not throw permission error in case it occurs on folder move', async(() => { spyOnDocumentListServiceAction = spyOn( documentListService, @@ -1029,41 +1128,29 @@ describe('NodeActionsService', () => { })); it('should not throw error on conflict in case it occurs on folder move', async(() => { - spyOnDocumentListServiceAction = spyOn( - documentListService, - 'moveNode' - ).and.returnValue(throwError(conflictError)); - const newDestination = new TestNode( 'new-destination', !isFile, folderToMove.entry.name - ); - spyOn(service, 'getChildByName').and.returnValue(of(newDestination)); + ) as NodeChildAssociationEntry; + + spyOnDocumentListServiceAction = spyOn( + documentListService, + 'moveNode' + ).and.returnValue(throwError(conflictError)); + spyOn(service, 'getChildByName').and.returnValue(subject$); spyOn(service, 'getNodeChildren').and.returnValue( of(emptyChildrenList) ); - const moveFolderActionObservable = service.moveFolderAction( - folderToMove.entry, - folderDestinationId - ); - moveFolderActionObservable - .toPromise() - .then( - () => { - spyOnSuccess(); - }, - error => { - spyOnError(error); - } - ) - .then(() => { - expect(spyOnDocumentListServiceAction).toHaveBeenCalled(); + service + .moveFolderAction(folderToMove.entry, folderDestinationId) + .subscribe(spyOnSuccess, spyOnError); - expect(spyOnSuccess).toHaveBeenCalled(); - expect(spyOnError).not.toHaveBeenCalledWith(conflictError); - }); + subject$.next(newDestination); + + expect(spyOnSuccess).toHaveBeenCalled(); + expect(spyOnError).not.toHaveBeenCalledWith(conflictError); })); it('should try to move children nodes of a folder to already existing folder with same name', async(() => { @@ -1087,33 +1174,21 @@ describe('NodeActionsService', () => { 'new-destination', !isFile, 'conflicting-name' - ); - spyOn(service, 'getChildByName').and.returnValue(of(newDestination)); + ) as NodeChildAssociationEntry; + spyOn(service, 'getChildByName').and.returnValue(subject$); const childrenNodes = [fileToMove, folderToMove]; spyOn(service, 'getNodeChildren').and.returnValue( of({ list: { entries: childrenNodes } }) ); - const moveFolderActionObservable = service.moveFolderAction( - parentFolderToMove.entry, - folderDestinationId - ); - moveFolderActionObservable - .toPromise() - .then( - () => { - spyOnSuccess(); - }, - error => { - spyOnError(error); - } - ) - .then(() => { - expect(spyOnDocumentListServiceAction).toHaveBeenCalled(); + service + .moveFolderAction(parentFolderToMove.entry, folderDestinationId) + .subscribe(spyOnSuccess, spyOnError); + subject$.next(newDestination); - expect(spyOnSuccess).toHaveBeenCalled(); - expect(spyOnError).not.toHaveBeenCalledWith(conflictError); - }); + expect(spyOnDocumentListServiceAction).toHaveBeenCalled(); + expect(spyOnSuccess).toHaveBeenCalled(); + expect(spyOnError).not.toHaveBeenCalledWith(conflictError); })); }); @@ -1122,8 +1197,10 @@ describe('NodeActionsService', () => { let parentFolderToMove; let moveNodeActionPromise; let spyOnDelete; + let subject$: Subject; beforeEach(() => { + subject$ = new Subject(); parentFolderToMove = new TestNode( 'parent-folder', !isFile, @@ -1138,6 +1215,7 @@ describe('NodeActionsService', () => { spyOnDelete.calls.reset(); spyOnSuccess.calls.reset(); spyOnError.calls.reset(); + subject$.complete(); }); it('should take no extra delete action, if folder was moved to the same location', async(() => { @@ -1207,29 +1285,18 @@ describe('NodeActionsService', () => { partiallySucceeded: [] }); const folderOnLocation = parentFolderToMove; - spyOn(service, 'getChildByName').and.returnValue( - of(folderOnLocation) - ); + spyOn(service, 'getChildByName').and.returnValue(subject$); parentFolderToMove.entry.parentId = `not-${folderDestinationId}`; moveNodeActionPromise = service .moveNodeAction(parentFolderToMove.entry, folderDestinationId) - .toPromise(); - moveNodeActionPromise - .then( - () => { - spyOnSuccess(); - }, - error => { - spyOnError(error); - } - ) - .then(() => { - expect(spyOnDelete).toHaveBeenCalled(); + .subscribe(spyOnSuccess, spyOnError); + subject$.next(folderOnLocation); - expect(spyOnSuccess).toHaveBeenCalled(); - expect(spyOnError).not.toHaveBeenCalled(); - }); + expect(spyOnDelete).toHaveBeenCalled(); + + expect(spyOnSuccess).toHaveBeenCalled(); + expect(spyOnError).not.toHaveBeenCalled(); })); it('should take no extra delete action, if folder is no longer on location', async(() => { @@ -1242,27 +1309,17 @@ describe('NodeActionsService', () => { failed: [], partiallySucceeded: [] }); - spyOn(service, 'getChildByName').and.returnValue(of(null)); + spyOn(service, 'getChildByName').and.returnValue(subject$); parentFolderToMove.entry.parentId = `not-${folderDestinationId}`; moveNodeActionPromise = service .moveNodeAction(parentFolderToMove.entry, folderDestinationId) - .toPromise(); - moveNodeActionPromise - .then( - () => { - spyOnSuccess(); - }, - error => { - spyOnError(error); - } - ) - .then(() => { - expect(spyOnDelete).not.toHaveBeenCalled(); + .subscribe(spyOnSuccess, spyOnError); + subject$.next(null); - expect(spyOnSuccess).toHaveBeenCalled(); - expect(spyOnError).not.toHaveBeenCalled(); - }); + expect(spyOnDelete).not.toHaveBeenCalled(); + expect(spyOnSuccess).toHaveBeenCalled(); + expect(spyOnError).not.toHaveBeenCalled(); })); }); }); diff --git a/src/app/services/node-actions.service.ts b/src/app/services/node-actions.service.ts index 2977e4e33..10cd9cced 100644 --- a/src/app/services/node-actions.service.ts +++ b/src/app/services/node-actions.service.ts @@ -45,7 +45,8 @@ import { MinimalNodeEntity, MinimalNodeEntryEntity, SitePaging, - NodeChildAssociationPaging + NodeChildAssociationPaging, + NodeChildAssociationEntry } from '@alfresco/js-api'; import { ContentApiService } from '@alfresco/aca-shared'; import { catchError, map, mergeMap } from 'rxjs/operators'; @@ -624,11 +625,14 @@ export class NodeActionsService { ); } - getChildByName(parentId: string, name: string) { + getChildByName( + parentId: string, + name: string + ): Subject { const matchedNodes = new Subject(); this.getNodeChildren(parentId).subscribe( - (childrenNodes: any) => { + (childrenNodes: NodeChildAssociationPaging) => { const result = childrenNodes.list.entries.find( node => node.entry.name === name ); diff --git a/src/app/services/node-template.service.spec.ts b/src/app/services/node-template.service.spec.ts index 751a269b7..94dc63c27 100644 --- a/src/app/services/node-template.service.spec.ts +++ b/src/app/services/node-template.service.spec.ts @@ -32,7 +32,7 @@ import { Store } from '@ngrx/store'; import { MatDialog } from '@angular/material/dialog'; import { AlfrescoApiService, AlfrescoApiServiceMock } from '@alfresco/adf-core'; import { NodeTemplateService } from './node-template.service'; -import { of } from 'rxjs'; +import { ResultSetPaging } from '@alfresco/js-api'; describe('NodeTemplateService', () => { let dialog: MatDialog; @@ -65,7 +65,9 @@ describe('NodeTemplateService', () => { it('should open dialog with parent node `id` as data property', fakeAsync(() => { spyOn(alfrescoApiService.searchApi, 'search').and.returnValue( - of({ list: { entries: [{ entry: { id: 'parent-node-id' } }] } }) + Promise.resolve({ + list: { entries: [{ entry: { id: 'parent-node-id' } }] } + } as ResultSetPaging) ); spyOn(dialog, 'open'); @@ -79,7 +81,7 @@ describe('NodeTemplateService', () => { it('should remove parents path for templates breadcrumb', fakeAsync(() => { spyOn(alfrescoApiService.searchApi, 'search').and.returnValue( - of({ + Promise.resolve({ list: { entries: [ { @@ -96,11 +98,12 @@ describe('NodeTemplateService', () => { } ] } - }) + } as ResultSetPaging) ); spyOn(dialog, 'open'); nodeTemplateService.selectTemplateDialog(fileTemplateConfig); + tick(); const breadcrumb = dialog.open['calls'] .argsFor(0)[1] @@ -120,7 +123,7 @@ describe('NodeTemplateService', () => { it('should set template folder path as root for breadcrumb', fakeAsync(() => { spyOn(alfrescoApiService.searchApi, 'search').and.returnValue( - of({ + Promise.resolve({ list: { entries: [ { @@ -137,11 +140,12 @@ describe('NodeTemplateService', () => { } ] } - }) + } as ResultSetPaging) ); spyOn(dialog, 'open'); nodeTemplateService.selectTemplateDialog(fileTemplateConfig); + tick(); const breadcrumb = dialog.open['calls'] .argsFor(0)[1] @@ -178,9 +182,9 @@ describe('NodeTemplateService', () => { ); })); - it('should return true if row is not a `link` nodeType', () => { + it('should return true if row is not a `link` nodeType', fakeAsync(() => { spyOn(alfrescoApiService.searchApi, 'search').and.returnValue( - of({ + Promise.resolve({ list: { entries: [ { @@ -194,22 +198,23 @@ describe('NodeTemplateService', () => { } ] } - }) + } as ResultSetPaging) ); spyOn(dialog, 'open'); nodeTemplateService.selectTemplateDialog(fileTemplateConfig); + tick(); expect( dialog.open['calls'].argsFor(0)[1].data.rowFilter({ node: { entry: { nodeType: 'text' } } }) ).toBe(true); - }); + })); - it('should return false if row is a `filelink` nodeType', () => { + it('should return false if row is a `filelink` nodeType', fakeAsync(() => { spyOn(alfrescoApiService.searchApi, 'search').and.returnValue( - of({ + Promise.resolve({ list: { entries: [ { @@ -223,22 +228,23 @@ describe('NodeTemplateService', () => { } ] } - }) + } as ResultSetPaging) ); spyOn(dialog, 'open'); nodeTemplateService.selectTemplateDialog(fileTemplateConfig); + tick(); expect( dialog.open['calls'].argsFor(0)[1].data.rowFilter({ node: { entry: { nodeType: 'app:filelink' } } }) ).toBe(false); - }); + })); - it('should return false if row is a `folderlink` nodeType', () => { + it('should return false if row is a `folderlink` nodeType', fakeAsync(() => { spyOn(alfrescoApiService.searchApi, 'search').and.returnValue( - of({ + Promise.resolve({ list: { entries: [ { @@ -252,27 +258,31 @@ describe('NodeTemplateService', () => { } ] } - }) + } as ResultSetPaging) ); spyOn(dialog, 'open'); nodeTemplateService.selectTemplateDialog(fileTemplateConfig); + tick(); expect( dialog.open['calls'].argsFor(0)[1].data.rowFilter({ node: { entry: { nodeType: 'app:folderlink' } } }) ).toBe(false); - }); + })); describe('File templates', () => { - it('should return false if selected node is not a file', () => { + it('should return false if selected node is not a file', fakeAsync(() => { spyOn(alfrescoApiService.searchApi, 'search').and.returnValue( - of({ list: { entries: [{ entry: { id: 'templates-folder-id' } }] } }) + Promise.resolve({ + list: { entries: [{ entry: { id: 'templates-folder-id' } }] } + } as ResultSetPaging) ); spyOn(dialog, 'open'); nodeTemplateService.selectTemplateDialog(fileTemplateConfig); + tick(); const isSelectionValid = dialog.open['calls'] .argsFor(0)[1] @@ -284,15 +294,18 @@ describe('NodeTemplateService', () => { }); expect(isSelectionValid).toBe(false); - }); + })); - it('should return true if selected node is a template file', () => { + it('should return true if selected node is a template file', fakeAsync(() => { spyOn(alfrescoApiService.searchApi, 'search').and.returnValue( - of({ list: { entries: [{ entry: { id: 'templates-folder-id' } }] } }) + Promise.resolve({ + list: { entries: [{ entry: { id: 'templates-folder-id' } }] } + } as ResultSetPaging) ); spyOn(dialog, 'open'); nodeTemplateService.selectTemplateDialog(fileTemplateConfig); + tick(); const isSelectionValid = dialog.open['calls'] .argsFor(0)[1] @@ -304,30 +317,36 @@ describe('NodeTemplateService', () => { }); expect(isSelectionValid).toBe(true); - }); + })); - it('should set dialog title for file templates', () => { + it('should set dialog title for file templates', fakeAsync(() => { spyOn(alfrescoApiService.searchApi, 'search').and.returnValue( - of({ list: { entries: [{ entry: { id: 'templates-folder-id' } }] } }) + Promise.resolve({ + list: { entries: [{ entry: { id: 'templates-folder-id' } }] } + } as ResultSetPaging) ); spyOn(dialog, 'open'); nodeTemplateService.selectTemplateDialog(fileTemplateConfig); + tick(); const title = dialog.open['calls'].argsFor(0)[1].data.title; expect(title).toBe('NODE_SELECTOR.SELECT_FILE_TEMPLATE_TITLE'); - }); + })); }); describe('Folder templates', () => { - it('should return false if selected node is not a folder', () => { + it('should return false if selected node is not a folder', fakeAsync(() => { spyOn(alfrescoApiService.searchApi, 'search').and.returnValue( - of({ list: { entries: [{ entry: { id: 'templates-folder-id' } }] } }) + Promise.resolve({ + list: { entries: [{ entry: { id: 'templates-folder-id' } }] } + } as ResultSetPaging) ); spyOn(dialog, 'open'); nodeTemplateService.selectTemplateDialog(folderTemplateConfig); + tick(); const isSelectionValid = dialog.open['calls'] .argsFor(0)[1] @@ -339,15 +358,18 @@ describe('NodeTemplateService', () => { }); expect(isSelectionValid).toBe(false); - }); + })); it('should return false if current node is the parent folder', fakeAsync(() => { spyOn(alfrescoApiService.searchApi, 'search').and.returnValue( - of({ list: { entries: [{ entry: { id: 'templates-folder-id' } }] } }) + Promise.resolve({ + list: { entries: [{ entry: { id: 'templates-folder-id' } }] } + } as ResultSetPaging) ); spyOn(dialog, 'open'); nodeTemplateService.selectTemplateDialog(folderTemplateConfig); + tick(); const isSelectionValid = dialog.open['calls'] .argsFor(0)[1] @@ -363,13 +385,13 @@ describe('NodeTemplateService', () => { it('should return true if selected node is a folder template', fakeAsync(() => { spyOn(alfrescoApiService.searchApi, 'search').and.returnValue( - of({ + Promise.resolve({ list: { entries: [ { entry: { id: 'templates-folder-id', path: { elements: [] } } } ] } - }) + } as ResultSetPaging) ); spyOn(dialog, 'open'); @@ -388,17 +410,20 @@ describe('NodeTemplateService', () => { expect(isSelectionValid).toBe(true); })); - it('should set dialog title for folder templates', () => { + it('should set dialog title for folder templates', fakeAsync(() => { spyOn(alfrescoApiService.searchApi, 'search').and.returnValue( - of({ list: { entries: [{ entry: { id: 'templates-folder-id' } }] } }) + Promise.resolve({ + list: { entries: [{ entry: { id: 'templates-folder-id' } }] } + } as ResultSetPaging) ); spyOn(dialog, 'open'); nodeTemplateService.selectTemplateDialog(folderTemplateConfig); + tick(); const title = dialog.open['calls'].argsFor(0)[1].data.title; expect(title).toBe('NODE_SELECTOR.SELECT_FOLDER_TEMPLATE_TITLE'); - }); + })); }); }); diff --git a/src/app/store/effects/contextmenu.effects.spec.ts b/src/app/store/effects/contextmenu.effects.spec.ts index 0e992efad..858ee3c29 100644 --- a/src/app/store/effects/contextmenu.effects.spec.ts +++ b/src/app/store/effects/contextmenu.effects.spec.ts @@ -30,13 +30,13 @@ import { EffectsModule } from '@ngrx/effects'; import { Store } from '@ngrx/store'; import { ContextMenu } from '@alfresco/aca-shared/store'; import { ContextMenuService } from '../../components/context-menu/context-menu.service'; +import { OverlayRef } from '@angular/cdk/overlay'; +import { ContextMenuOverlayRef } from '../../components/context-menu/context-menu-overlay'; describe('ContextMenuEffects', () => { let store: Store; let contextMenuService: ContextMenuService; - const overlayRefMock = { - close: jasmine.createSpy('close') - }; + const overlayRefMock = new ContextMenuOverlayRef({} as OverlayRef); beforeEach(() => { TestBed.configureTestingModule({ @@ -47,6 +47,7 @@ describe('ContextMenuEffects', () => { store = TestBed.get(Store); contextMenuService = TestBed.get(ContextMenuService); + spyOn(overlayRefMock, 'close').and.callFake(() => {}); spyOn(contextMenuService, 'open').and.returnValue(overlayRefMock); }); diff --git a/src/app/store/effects/template.effects.spec.ts b/src/app/store/effects/template.effects.spec.ts index 28deb86e9..ef61cddeb 100644 --- a/src/app/store/effects/template.effects.spec.ts +++ b/src/app/store/effects/template.effects.spec.ts @@ -36,11 +36,12 @@ import { SnackbarErrorAction } from '@alfresco/aca-shared/store'; import { NodeTemplateService } from '../../services/node-template.service'; -import { of } from 'rxjs'; +import { of, Subject } from 'rxjs'; import { AlfrescoApiServiceMock, AlfrescoApiService } from '@alfresco/adf-core'; import { ContentManagementService } from '../../services/content-management.service'; import { Node, NodeEntry } from '@alfresco/js-api'; -import { MatDialog } from '@angular/material/dialog'; +import { MatDialog, MatDialogRef } from '@angular/material/dialog'; +import { CreateFromTemplateDialogComponent } from '../../dialogs/node-template/create-from-template.dialog'; describe('TemplateEffects', () => { let store: Store; @@ -75,6 +76,8 @@ describe('TemplateEffects', () => { selectionType: 'folder' }; + let subject: Subject; + beforeEach(() => { TestBed.configureTestingModule({ imports: [AppTestingModule, EffectsModule.forRoot([TemplateEffects])], @@ -95,13 +98,12 @@ describe('TemplateEffects', () => { alfrescoApiService = TestBed.get(AlfrescoApiService); contentManagementService = TestBed.get(ContentManagementService); matDialog = TestBed.get(MatDialog); + subject = new Subject(); spyOn(store, 'dispatch').and.callThrough(); spyOn(contentManagementService.reload, 'next'); spyOn(store, 'select').and.returnValue(of({ id: 'parent-id' })); - spyOn(nodeTemplateService, 'selectTemplateDialog').and.returnValue( - of([{ id: 'template-id' }]) - ); + spyOn(nodeTemplateService, 'selectTemplateDialog').and.returnValue(subject); copyNodeSpy = spyOn(alfrescoApiService.getInstance().nodes, 'copyNode'); updateNodeSpy = spyOn(alfrescoApiService.getInstance().nodes, 'updateNode'); @@ -112,10 +114,28 @@ describe('TemplateEffects', () => { updateNodeSpy.calls.reset(); }); + it('should call createTemplateDialog on FileFromTemplate action', fakeAsync(() => { + spyOn(nodeTemplateService, 'createTemplateDialog'); + store.dispatch(new FileFromTemplate()); + subject.next([node]); + tick(300); + + expect(nodeTemplateService.createTemplateDialog).toHaveBeenCalledWith(node); + })); + + it('should call createTemplateDialog on FolderFromTemplate action', fakeAsync(() => { + spyOn(nodeTemplateService, 'createTemplateDialog'); + store.dispatch(new FolderFromTemplate()); + subject.next([node]); + tick(300); + + expect(nodeTemplateService.createTemplateDialog).toHaveBeenCalledWith(node); + })); + it('should open dialog to select template files', fakeAsync(() => { spyOn(nodeTemplateService, 'createTemplateDialog').and.returnValue({ afterClosed: () => of(node) - }); + } as MatDialogRef); store.dispatch(new FileFromTemplate()); tick(); @@ -128,7 +148,7 @@ describe('TemplateEffects', () => { it('should open dialog to select template folders', fakeAsync(() => { spyOn(nodeTemplateService, 'createTemplateDialog').and.returnValue({ afterClosed: () => of(node) - }); + } as MatDialogRef); store.dispatch(new FolderFromTemplate()); tick(); diff --git a/src/app/store/effects/upload.effects.spec.ts b/src/app/store/effects/upload.effects.spec.ts index 376c56bf0..24d0c12da 100644 --- a/src/app/store/effects/upload.effects.spec.ts +++ b/src/app/store/effects/upload.effects.spec.ts @@ -34,12 +34,15 @@ import { FileUploadCompleteEvent, FileModel } from '@alfresco/adf-core'; +import { MinimalNodeEntryEntity } from '@alfresco/js-api'; import { UnlockWriteAction, SnackbarErrorAction } from '@alfresco/aca-shared/store'; import { ContentManagementService } from '../../services/content-management.service'; import { of, throwError } from 'rxjs'; +import { MatDialogRef } from '@angular/material'; +import { NodeVersionUploadDialogComponent } from '../../dialogs/node-version-upload/node-version-upload.dialog'; function createFileList(fileName, type = 'text/plain') { const data = new Blob([''], { type }); @@ -198,7 +201,9 @@ describe('UploadEffects', () => { describe('upload file version', () => { beforeEach(() => { - const dialog = { afterClosed: () => of({}) }; + const dialog = { afterClosed: () => of({}) } as MatDialogRef< + NodeVersionUploadDialogComponent + >; spyOn(contentManagementService, 'versionUploadDialog').and.returnValue( dialog ); @@ -208,11 +213,9 @@ describe('UploadEffects', () => { it('should upload file', () => { spyOn(contentManagementService, 'getNodeInfo').and.returnValue( of({ - entry: { - id: 'file1', - properties: {} - } - }) + id: 'file1', + properties: {} + } as MinimalNodeEntryEntity) ); uploadVersionInput.files = createFileList('bogus.txt');