intermediate commit

This commit is contained in:
lotte
2018-12-13 11:19:50 +01:00
parent f1da199cde
commit c6b7fd40e2
20 changed files with 1225 additions and 47 deletions

View File

@@ -0,0 +1,78 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { TranslateModule } from '@ngx-translate/core';
import { ChangeDetectionStrategy, Injector, NO_ERRORS_SCHEMA } from '@angular/core';
import { AdminSidebarComponent } from './admin-sidebar.component';
import { MenuService } from '../../shared/menu/menu.service';
import { MenuServiceStub } from '../../shared/testing/menu-service-stub';
import { CSSVariableService } from '../../shared/sass-helper/sass-helper.service';
import { CSSVariableServiceStub } from '../../shared/testing/css-variable-service-stub';
import { AuthServiceStub } from '../../shared/testing/auth-service-stub';
import { AuthService } from '../../core/auth/auth.service';
import { NgComponentOutlet } from '@angular/common';
import { MockDirective } from 'ng-mocks';
fdescribe('AdminSidebarComponent', () => {
let comp: AdminSidebarComponent;
let fixture: ComponentFixture<AdminSidebarComponent>;
let menuService: AdminSidebarComponent;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [TranslateModule.forRoot(), NoopAnimationsModule],
declarations: [AdminSidebarComponent, MockDirective(NgComponentOutlet)],
providers: [
{ provide: Injector, useValue: {} },
{ provide: MenuService, useClass: MenuServiceStub },
{ provide: CSSVariableService, useClass: CSSVariableServiceStub },
{ provide: AuthService, useClass: AuthServiceStub }
],
schemas: [NO_ERRORS_SCHEMA]
}).overrideComponent(AdminSidebarComponent, {
set: { changeDetection: ChangeDetectionStrategy.Default }
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(AdminSidebarComponent);
comp = fixture.componentInstance; // SearchPageComponent test instance
menuService = (comp as any).menuService;
// spyOn(comp as any, 'getSectionDataInjector').and.returnValue(new Map());
// spyOn(comp as any, 'getSectionComponent').and.returnValue(observableOf(MenuSection));
fixture.detectChanges();
});
describe('startSlide', () => {
describe('when expanding', () => {
beforeEach(() => {
comp.sidebarClosed = true;
comp.startSlide({ toState: 'expanded' } as any);
});
it('should set the sidebarClosed to false', () => {
expect(comp.sidebarClosed).toBeFalsy();
})
});
describe('when collapsing', () => {
beforeEach(() => {
comp.sidebarClosed = false;
comp.startSlide({ toState: 'collapsed' } as any);
});
it('should set the sidebarClosed to false', () => {
expect(comp.sidebarClosed).toBeTruthy();
})
})
});
// describe('expand', () => {
// beforeEach(() => {
// spyOn(menuService, 'expandMenu');
// comp.expand(new Event('click'));
// });
// it('should trigger the expandMenu function on the menu service', () => {
// expect(menuService.expandMenu).toHaveBeenCalledWith(comp.menuID);
// })
// });
});

View File

@@ -84,7 +84,7 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
/**
* Initialize all menu sections and items for this menu
*/
createMenu() {
private createMenu() {
const menuList = [
/* News */
{

View File

@@ -0,0 +1,50 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { TranslateModule } from '@ngx-translate/core';
import { DebugElement, NO_ERRORS_SCHEMA } from '@angular/core';
import { By } from '@angular/platform-browser';
import { LinkMenuItemComponent } from './link-menu-item.component';
import { RouterLinkDirectiveStub } from '../../testing/router-link-directive-stub';
describe('LinkMenuItemComponent', () => {
let component: LinkMenuItemComponent;
let fixture: ComponentFixture<LinkMenuItemComponent>;
let debugElement: DebugElement;
const text = 'HELLO';
const link = 'http://google.com';
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [TranslateModule.forRoot()],
declarations: [LinkMenuItemComponent, RouterLinkDirectiveStub],
providers: [
{ provide: 'itemModelProvider', useValue: { text: text, link: link } },
],
schemas: [NO_ERRORS_SCHEMA]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(LinkMenuItemComponent);
component = fixture.componentInstance;
debugElement = fixture.debugElement;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should contain the correct text', () => {
const textContent = debugElement.query(By.css('a')).nativeElement.textContent;
expect(textContent).toEqual(text);
});
it('should have the right routerLink attribute', () => {
const linkDes = fixture.debugElement.queryAll(By.directive(RouterLinkDirectiveStub));
const routerLinkQuery = linkDes.map((de) => de.injector.get(RouterLinkDirectiveStub));
expect(routerLinkQuery.length).toBe(1);
expect(routerLinkQuery[0].routerLink).toBe(link);
});
});

View File

@@ -12,8 +12,8 @@ import { rendersMenuItemForType } from '../menu-item.decorator';
})
@rendersMenuItemForType(MenuItemType.LINK)
export class LinkMenuItemComponent {
@Input() item: LinkMenuItemModel;
constructor(@Inject('itemModelProvider') item) {
item: LinkMenuItemModel;
constructor(@Inject('itemModelProvider') item: LinkMenuItemModel) {
this.item = item;
}
}

View File

@@ -0,0 +1,39 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { TextMenuItemComponent } from './text-menu-item.component';
import { TranslateModule } from '@ngx-translate/core';
import { DebugElement, NO_ERRORS_SCHEMA } from '@angular/core';
import { By } from '@angular/platform-browser';
describe('TextMenuItemComponent', () => {
let component: TextMenuItemComponent;
let fixture: ComponentFixture<TextMenuItemComponent>;
let debugElement: DebugElement;
const text = 'HELLO';
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [TranslateModule.forRoot()],
declarations: [TextMenuItemComponent],
providers: [
{ provide: 'itemModelProvider', useValue: { text: text } },
],
schemas: [ NO_ERRORS_SCHEMA ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(TextMenuItemComponent);
component = fixture.componentInstance;
debugElement = fixture.debugElement;
fixture.detectChanges();
});
it('should contain the correct text', () => {
expect(component).toBeTruthy();
});
it('should contain the text element', () => {
const textContent = debugElement.query(By.css('span')).nativeElement.textContent;
expect(textContent).toEqual(text);
});
});

View File

@@ -12,8 +12,8 @@ import { rendersMenuItemForType } from '../menu-item.decorator';
})
@rendersMenuItemForType(MenuItemType.TEXT)
export class TextMenuItemComponent {
@Input() item: TextMenuItemModel;
constructor(@Inject('itemModelProvider') item) {
item: TextMenuItemModel;
constructor(@Inject('itemModelProvider') item: TextMenuItemModel) {
this.item = item;
}
}

View File

@@ -1,25 +1,76 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { TranslateModule } from '@ngx-translate/core';
import { ChangeDetectionStrategy, Injector, NO_ERRORS_SCHEMA } from '@angular/core';
import { MenuSectionComponent } from './menu-section.component';
import { MenuService } from '../menu.service';
import { MenuServiceStub } from '../../testing/menu-service-stub';
import { MenuSection } from '../menu.reducer';
import { of as observableOf } from 'rxjs';
import { LinkMenuItemComponent } from '../menu-item/link-menu-item.component';
describe('MenuSectionComponent', () => {
let component: MenuSectionComponent;
let comp: MenuSectionComponent;
let fixture: ComponentFixture<MenuSectionComponent>;
let menuService: MenuService;
const dummySection = {
id: 'section',
visible: true,
active: false
} as any;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ MenuSectionComponent ]
})
.compileComponents();
imports: [TranslateModule.forRoot(), NoopAnimationsModule],
declarations: [MenuSectionComponent],
providers: [
{ provide: Injector, useValue: {} },
{ provide: MenuService, useClass: MenuServiceStub },
{ provide: MenuSection, useValue: dummySection },
],
schemas: [NO_ERRORS_SCHEMA]
}).overrideComponent(MenuSectionComponent, {
set: { changeDetection: ChangeDetectionStrategy.Default }
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(MenuSectionComponent);
component = fixture.componentInstance;
comp = fixture.componentInstance;
menuService = (comp as any).menuService;
spyOn(comp as any, 'getMenuItemComponent').and.returnValue(LinkMenuItemComponent);
spyOn(comp as any, 'getItemModelInjector').and.returnValue(observableOf({}));
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
describe('toggleSection', () => {
beforeEach(() => {
spyOn(menuService, 'toggleActiveSection');
comp.toggleSection(new Event('click'));
});
it('should trigger the toggleActiveSection function on the menu service', () => {
expect(menuService.toggleActiveSection).toHaveBeenCalledWith(comp.menuID, dummySection.id);
})
});
describe('activateSection', () => {
beforeEach(() => {
spyOn(menuService, 'activateSection');
comp.activateSection(new Event('click'));
});
it('should trigger the activateSection function on the menu service', () => {
expect(menuService.activateSection).toHaveBeenCalledWith(comp.menuID, dummySection.id);
})
});
describe('deactivateSection', () => {
beforeEach(() => {
spyOn(menuService, 'deactivateSection');
comp.deactivateSection(new Event('click'));
});
it('should trigger the deactivateSection function on the menu service', () => {
expect(menuService.deactivateSection).toHaveBeenCalledWith(comp.menuID, dummySection.id);
})
});
});

View File

@@ -84,7 +84,7 @@ export class MenuSectionComponent {
/**
* Method for initializing all injectors and component constructors for the menu items in this section
*/
initializeInjectorData() {
private initializeInjectorData() {
this.itemInjectors.set(this.section.id, this.getItemModelInjector(this.section.model));
this.itemComponents.set(this.section.id, this.getMenuItemComponent(this.section.model));
this.subSections = this.menuService.getSubSectionsByParentID(this.menuID, this.section.id);
@@ -101,7 +101,7 @@ export class MenuSectionComponent {
* @param {MenuItemModel} itemModel The given MenuItemModel
* @returns {GenericConstructor} Emits the constructor of the Component that should be used to render this menu item model
*/
getMenuItemComponent(itemModel?: MenuItemModel) {
private getMenuItemComponent(itemModel?: MenuItemModel) {
if (hasNoValue(itemModel)) {
itemModel = this.section.model;
}
@@ -114,7 +114,7 @@ export class MenuSectionComponent {
* @param {MenuItemModel} itemModel The given MenuItemModel
* @returns {Injector} The Injector that injects the data for this menu item into the item's component
*/
getItemModelInjector(itemModel?: MenuItemModel) {
private getItemModelInjector(itemModel?: MenuItemModel) {
if (hasNoValue(itemModel)) {
itemModel = this.section.model;
}

View File

@@ -0,0 +1,88 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { TranslateModule } from '@ngx-translate/core';
import { ChangeDetectionStrategy, Injector, NO_ERRORS_SCHEMA } from '@angular/core';
import { MenuService } from './menu.service';
import { MenuComponent } from './menu.component';
import { MenuServiceStub } from '../testing/menu-service-stub';
import { of as observableOf } from 'rxjs';
import { MenuSection } from './menu.reducer';
describe('MenuComponent', () => {
let comp: MenuComponent;
let fixture: ComponentFixture<MenuComponent>;
let menuService: MenuService;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [TranslateModule.forRoot(), NoopAnimationsModule],
declarations: [MenuComponent],
providers: [
{ provide: Injector, useValue: {} },
{ provide: MenuService, useClass: MenuServiceStub },
],
schemas: [NO_ERRORS_SCHEMA]
}).overrideComponent(MenuComponent, {
set: { changeDetection: ChangeDetectionStrategy.Default }
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(MenuComponent);
comp = fixture.componentInstance; // SearchPageComponent test instance
menuService = (comp as any).menuService;
spyOn(comp as any, 'getSectionDataInjector').and.returnValue(MenuSection);
spyOn(comp as any, 'getSectionComponent').and.returnValue(observableOf({}));
fixture.detectChanges();
});
describe('toggle', () => {
beforeEach(() => {
spyOn(menuService, 'toggleMenu');
comp.toggle(new Event('click'));
});
it('should trigger the toggleMenu function on the menu service', () => {
expect(menuService.toggleMenu).toHaveBeenCalledWith(comp.menuID);
})
});
describe('expand', () => {
beforeEach(() => {
spyOn(menuService, 'expandMenu');
comp.expand(new Event('click'));
});
it('should trigger the expandMenu function on the menu service', () => {
expect(menuService.expandMenu).toHaveBeenCalledWith(comp.menuID);
})
});
describe('collapse', () => {
beforeEach(() => {
spyOn(menuService, 'collapseMenu');
comp.collapse(new Event('click'));
});
it('should trigger the collapseMenu function on the menu service', () => {
expect(menuService.collapseMenu).toHaveBeenCalledWith(comp.menuID);
})
});
describe('expandPreview', () => {
beforeEach(() => {
spyOn(menuService, 'expandMenuPreview');
comp.expandPreview(new Event('click'));
});
it('should trigger the expandPreview function on the menu service', () => {
expect(menuService.expandMenuPreview).toHaveBeenCalledWith(comp.menuID);
})
});
describe('collapsePreview', () => {
beforeEach(() => {
spyOn(menuService, 'collapseMenuPreview');
comp.collapsePreview(new Event('click'));
});
it('should trigger the collapsePreview function on the menu service', () => {
expect(menuService.collapseMenuPreview).toHaveBeenCalledWith(comp.menuID);
})
});
});

View File

@@ -125,7 +125,7 @@ export class MenuComponent implements OnInit {
* @param {MenuSection} section The given MenuSection
* @returns {Observable<GenericConstructor<MenuSectionComponent>>} Emits the constructor of the Component that should be used to render this object
*/
getSectionComponent(section: MenuSection): Observable<GenericConstructor<MenuSectionComponent>> {
private getSectionComponent(section: MenuSection): Observable<GenericConstructor<MenuSectionComponent>> {
return this.menuService.hasSubSections(this.menuID, section.id).pipe(
map((expandable: boolean) => {
return getComponentForMenu(this.menuID, expandable);
@@ -139,7 +139,7 @@ export class MenuComponent implements OnInit {
* @param {MenuSection} section The given MenuSection
* @returns {Injector} The Injector that injects the data for this menu section into the section's component
*/
getSectionDataInjector(section: MenuSection) {
private getSectionDataInjector(section: MenuSection) {
return Injector.create({
providers: [{ provide: 'sectionDataProvider', useFactory: () => (section), deps: [] }],
parent: this.injector

View File

@@ -1,13 +1,467 @@
// import { initialState, reducer } from './menu.reducer';
//
// describe('Menu Reducer', () => {
// describe('unknown action', () => {
// it('should return the initial state', () => {
// const action = {} as any;
//
// const result = reducer(initialState, action);
//
// expect(result).toBe(initialState);
// });
// });
// });
import * as deepFreeze from 'deep-freeze';
import {
ActivateMenuSectionAction,
AddMenuSectionAction,
CollapseMenuAction,
CollapseMenuPreviewAction,
DeactivateMenuSectionAction,
ExpandMenuAction,
ExpandMenuPreviewAction,
HideMenuAction,
HideMenuSectionAction,
RemoveMenuSectionAction,
ShowMenuAction,
ShowMenuSectionAction,
ToggleActiveMenuSectionAction,
ToggleMenuAction
} from './menu.actions';
import { MenuSectionIndex, menusReducer } from './menu.reducer';
import { initialMenusState, MenuID } from './initial-menus-state';
let visibleSection1;
let dummyState;
const menuID = MenuID.ADMIN;
const topSectionID = 'new';
class NullAction extends CollapseMenuAction {
type = null;
constructor() {
super(undefined);
}
}
describe('menusReducer', () => {
beforeEach(() => {
visibleSection1 = {
id: 'section',
parentID: 'new',
visible: true,
active: false,
index: -1,
};
dummyState = {
[MenuID.ADMIN]: {
id: MenuID.ADMIN,
collapsed: true,
previewCollapsed: true,
visible: true,
sections: {
[topSectionID]: {
id: topSectionID,
active: false,
visible: true,
model: {
type: 0,
text: 'admin.sidebar.section.new'
},
icon: 'plus-circle',
index: 0
},
new_item: {
id: 'new_item',
parentID: 'new',
active: false,
visible: true,
model: {
type: 1,
text: 'admin.sidebar.section.new_item',
link: '/items/submission'
}
},
new_community: {
id: 'new_community',
parentID: 'new',
active: false,
visible: true,
model: {
type: 1,
text: 'admin.sidebar.section.new_community',
link: '/communities/submission'
}
},
access_control: {
id: 'access_control',
active: false,
visible: true,
model: {
type: 0,
text: 'admin.sidebar.section.access_control'
},
icon: 'key',
index: 4
},
access_control_people: {
id: 'access_control_people',
parentID: 'access_control',
active: false,
visible: true,
model: {
type: 1,
text: 'admin.sidebar.section.access_control_people',
link: '#'
}
},
access_control_groups: {
id: 'access_control_groups',
parentID: 'access_control',
active: false,
visible: true,
model: {
type: 1,
text: 'admin.sidebar.section.access_control_groups',
link: '#'
}
},
new_collection: {
id: 'new_collection',
parentID: 'new',
active: false,
visible: true,
model: {
type: 1,
text: 'admin.sidebar.section.new_collection',
link: '/collections/submission'
}
}
},
sectionToSubsectionIndex: {
access_control: [
'access_control_people',
'access_control_groups',
],
new: [
'new_collection',
'new_item',
'new_community'
]
}
}
}
});
it('should return the current state when no valid actions have been made', () => {
const state = dummyState;
const action = new NullAction();
const newState = menusReducer(state, action);
expect(newState).toEqual(state);
});
it('should start with the initialMenusState', () => {
const state = initialMenusState;
const action = new NullAction();
const initialState = menusReducer(undefined, action);
// The search filter starts collapsed
expect(initialState).toEqual(state);
});
it('should set collapsed to true for the correct menu in response to the COLLAPSE_MENU action', () => {
dummyState[MenuID.ADMIN].collapsed = false;
const state = dummyState;
const action = new CollapseMenuAction(menuID);
const newState = menusReducer(state, action);
expect(newState[menuID].collapsed).toEqual(true);
});
it('should perform the COLLAPSE_MENU action without affecting the previous state', () => {
dummyState[MenuID.ADMIN].collapsed = false;
const state = dummyState;
deepFreeze([state]);
const action = new CollapseMenuAction(menuID);
menusReducer(state, action);
// no expect required, deepFreeze will ensure an exception is thrown if the state
// is mutated, and any uncaught exception will cause the test to fail
});
it('should set collapsed to false for the correct menu in response to the EXPAND_MENU action', () => {
dummyState[MenuID.ADMIN].collapsed = true;
const state = dummyState;
const action = new ExpandMenuAction(menuID);
const newState = menusReducer(state, action);
expect(newState[menuID].collapsed).toEqual(false);
});
it('should perform the EXPAND_MENU action without affecting the previous state', () => {
dummyState[MenuID.ADMIN].collapsed = true;
const state = dummyState;
deepFreeze([state]);
const action = new ExpandMenuAction(menuID);
menusReducer(state, action);
// no expect required, deepFreeze will ensure an exception is thrown if the state
// is mutated, and any uncaught exception will cause the test to fail
});
it('should set collapsed to false for the correct menu in response to the TOGGLE_MENU action when collapsed is true', () => {
dummyState[MenuID.ADMIN].collapsed = true;
const state = dummyState;
const action = new ToggleMenuAction(menuID);
const newState = menusReducer(state, action);
expect(newState[menuID].collapsed).toEqual(false);
});
it('should set collapsed to true for the correct menu in response to the TOGGLE_MENU action when collapsed is false', () => {
dummyState[MenuID.ADMIN].collapsed = true;
const state = dummyState;
const action = new ToggleMenuAction(menuID);
const newState = menusReducer(state, action);
expect(newState[menuID].collapsed).toEqual(false);
});
it('should perform the TOGGLE_MENU action without affecting the previous state', () => {
dummyState[MenuID.ADMIN].collapsed = true;
const state = dummyState;
deepFreeze([state]);
const action = new ToggleMenuAction(menuID);
menusReducer(state, action);
// no expect required, deepFreeze will ensure an exception is thrown if the state
// is mutated, and any uncaught exception will cause the test to fail
});
it('should set previewCollapsed to true for the correct menu in response to the COLLAPSE_MENU_PREVIEW action', () => {
dummyState[MenuID.ADMIN].previewCollapsed = false;
const state = dummyState;
const action = new CollapseMenuPreviewAction(menuID);
const newState = menusReducer(state, action);
expect(newState[menuID].previewCollapsed).toEqual(true);
});
it('should perform the COLLAPSE_MENU_PREVIEW action without affecting the previous state', () => {
dummyState[MenuID.ADMIN].previewCollapsed = false;
const state = dummyState;
deepFreeze([state]);
const action = new CollapseMenuPreviewAction(menuID);
menusReducer(state, action);
// no expect required, deepFreeze will ensure an exception is thrown if the state
// is mutated, and any uncaught exception will cause the test to fail
});
it('should set previewCollapsed to false for the correct menu in response to the EXPAND_MENU_PREVIEW action', () => {
dummyState[MenuID.ADMIN].previewCollapsed = true;
const state = dummyState;
const action = new ExpandMenuPreviewAction(menuID);
const newState = menusReducer(state, action);
expect(newState[menuID].previewCollapsed).toEqual(false);
});
it('should perform the EXPAND_MENU_PREVIEW action without affecting the previous state', () => {
dummyState[MenuID.ADMIN].previewCollapsed = true;
const state = dummyState;
deepFreeze([state]);
const action = new ExpandMenuPreviewAction(menuID);
menusReducer(state, action);
// no expect required, deepFreeze will ensure an exception is thrown if the state
// is mutated, and any uncaught exception will cause the test to fail
});
it('should set visible to true for the correct menu in response to the SHOW_MENU action', () => {
dummyState[MenuID.ADMIN].visible = false;
const state = dummyState;
const action = new ShowMenuAction(menuID);
const newState = menusReducer(state, action);
expect(newState[menuID].visible).toEqual(true);
});
it('should perform the SHOW_MENU action without affecting the previous state', () => {
dummyState[MenuID.ADMIN].visible = false;
const state = dummyState;
deepFreeze([state]);
const action = new ShowMenuAction(menuID);
menusReducer(state, action);
// no expect required, deepFreeze will ensure an exception is thrown if the state
// is mutated, and any uncaught exception will cause the test to fail
});
it('should set previewCollapsed to false for the correct menu in response to the HIDE_MENU action', () => {
dummyState[MenuID.ADMIN].visible = true;
const state = dummyState;
const action = new HideMenuAction(menuID);
const newState = menusReducer(state, action);
expect(newState[menuID].visible).toEqual(false);
});
it('should perform the HIDE_MENU action without affecting the previous state', () => {
dummyState[MenuID.ADMIN].visible = true;
const state = dummyState;
deepFreeze([state]);
const action = new HideMenuAction(menuID);
menusReducer(state, action);
// no expect required, deepFreeze will ensure an exception is thrown if the state
// is mutated, and any uncaught exception will cause the test to fail
});
it('should set add a new section for the correct menu in response to the ADD_SECTION action', () => {
const state = dummyState;
const action = new AddMenuSectionAction(menuID, visibleSection1);
const newState = menusReducer(state, action);
expect(Object.values(newState[menuID].sections)).toContain(visibleSection1);
});
it('should set add a new section in the right place according to the index for the correct menu in response to the ADD_SECTION action', () => {
const state = dummyState;
const action = new AddMenuSectionAction(menuID, visibleSection1);
const newState = menusReducer(state, action);
expect(Object.values(newState[menuID].sections)[0]).toEqual(visibleSection1);
});
it('should add the new section to the sectionToSubsectionIndex when it has a parentID in response to the ADD_SECTION action', () => {
const state = dummyState;
const action = new AddMenuSectionAction(menuID, visibleSection1);
const newState = menusReducer(state, action);
expect(newState[menuID].sectionToSubsectionIndex[visibleSection1.parentID]).toContain(visibleSection1.id)
});
it('should perform the ADD_SECTION action without affecting the previous state', () => {
const state = dummyState;
deepFreeze([state]);
const action = new AddMenuSectionAction(menuID, visibleSection1);
menusReducer(state, action);
// no expect required, deepFreeze will ensure an exception is thrown if the state
// is mutated, and any uncaught exception will cause the test to fail
});
it('should remove a section for the correct menu in response to the REMOVE_SECTION action', () => {
const sectionID = Object.keys(dummyState[menuID].sections)[0];
const state = dummyState;
const action = new RemoveMenuSectionAction(menuID, sectionID);
const newState = menusReducer(state, action);
expect(Object.keys(newState[menuID].sections)).not.toContain(sectionID);
});
it('should remove a section for the correct menu from the sectionToSubsectionIndex in response to the REMOVE_SECTION action', () => {
const index: MenuSectionIndex = dummyState[menuID].sectionToSubsectionIndex;
const parentID: string = Object.keys(index)[0];
const childID: string = index[parentID][0];
const state = dummyState;
const action = new RemoveMenuSectionAction(menuID, childID);
const newState = menusReducer(state, action);
expect(newState[menuID].sectionToSubsectionIndex[parentID]).not.toContain(childID);
});
it('should set active to true for the correct menu section in response to the ACTIVATE_SECTION action', () => {
dummyState[menuID].sections[topSectionID].active = false;
const state = dummyState;
const action = new ActivateMenuSectionAction(menuID, topSectionID);
const newState = menusReducer(state, action);
expect(newState[menuID].sections[topSectionID].active).toEqual(true);
});
it('should perform the ACTIVATE_SECTION action without affecting the previous state', () => {
dummyState[menuID].sections[topSectionID].active = false;
const state = dummyState;
deepFreeze([state]);
const action = new ActivateMenuSectionAction(menuID, topSectionID);
menusReducer(state, action);
});
it('should set active to false for the correct menu section in response to the DEACTIVATE_SECTION action', () => {
dummyState[menuID].sections[topSectionID].active = true;
const state = dummyState;
const action = new DeactivateMenuSectionAction(menuID, topSectionID);
const newState = menusReducer(state, action);
expect(newState[menuID].sections[topSectionID].active).toEqual(false);
});
it('should perform the DEACTIVATE_SECTION action without affecting the previous state', () => {
dummyState[MenuID.ADMIN].sections[topSectionID].active = false;
const state = dummyState;
deepFreeze([state]);
const action = new DeactivateMenuSectionAction(menuID, topSectionID);
menusReducer(state, action);
});
it('should set active to false for the correct menu in response to the TOGGLE_ACTIVE_SECTION action when active is true', () => {
dummyState[menuID].sections[topSectionID].active = true;
const state = dummyState;
const action = new ToggleActiveMenuSectionAction(menuID, topSectionID);
const newState = menusReducer(state, action);
expect(newState[menuID].sections[topSectionID].active).toEqual(false);
});
it('should set collapsed to true for the correct menu in response to the TOGGLE_ACTIVE_SECTION action when active is false', () => {
dummyState[menuID].sections[topSectionID].active = false;
const state = dummyState;
const action = new ToggleActiveMenuSectionAction(menuID, topSectionID);
const newState = menusReducer(state, action);
expect(newState[menuID].sections[topSectionID].active).toEqual(true);
});
it('should perform the TOGGLE_ACTIVE_SECTION action without affecting the previous state', () => {
dummyState[menuID].sections[topSectionID].active = true;
const state = dummyState;
const action = new ToggleActiveMenuSectionAction(menuID, topSectionID);
deepFreeze([state]);
menusReducer(state, action);
// no expect required, deepFreeze will ensure an exception is thrown if the state
// is mutated, and any uncaught exception will cause the test to fail
});
it('should set visible to true for the correct menu section in response to the SHOW_SECTION action', () => {
dummyState[menuID].sections[topSectionID].visible = false;
const state = dummyState;
const action = new ShowMenuSectionAction(menuID, topSectionID);
const newState = menusReducer(state, action);
expect(newState[menuID].sections[topSectionID].visible).toEqual(true);
});
it('should perform the SHOW_SECTION action without affecting the previous state', () => {
dummyState[menuID].sections[topSectionID].visible = false;
const state = dummyState;
deepFreeze([state]);
const action = new ShowMenuSectionAction(menuID, topSectionID);
menusReducer(state, action);
});
it('should set visible to false for the correct menu section in response to the HIDE_SECTION action', () => {
dummyState[menuID].sections[topSectionID].visible = true;
const state = dummyState;
const action = new HideMenuSectionAction(menuID, topSectionID);
const newState = menusReducer(state, action);
expect(newState[menuID].sections[topSectionID].visible).toEqual(false);
});
it('should perform the HIDE_SECTION action without affecting the previous state', () => {
dummyState[MenuID.ADMIN].sections[topSectionID].visible = false;
const state = dummyState;
deepFreeze([state]);
const action = new HideMenuSectionAction(menuID, topSectionID);
menusReducer(state, action);
});
});

View File

@@ -175,9 +175,9 @@ function reorderSections(state: MenusState, action: MenuSectionAction) {
function removeSection(state: MenusState, action: RemoveMenuSectionAction) {
const menuState: MenuState = state[action.menuID];
const id = action.id;
const newMenuState = Object.assign({}, menuState);
delete newMenuState[id];
const newState = removeFromIndex(state, menuState.sections[action.id], action.menuID);
const newMenuState = Object.assign({}, newState[action.menuID]);
delete newMenuState.sections[id];
return Object.assign({}, newState, { [action.menuID]: newMenuState });
}
@@ -195,7 +195,7 @@ function removeFromIndex(state: MenusState, section: MenuSection, menuID: MenuID
const menuState: MenuState = state[menuID];
const index = menuState.sectionToSubsectionIndex;
const parentIndex = hasValue(index[parentID]) ? index[parentID] : [];
const newIndex = Object.assign({}, index, { [parentID]: parentIndex.filter((id) => id === sectionID) });
const newIndex = Object.assign({}, index, { [parentID]: parentIndex.filter((id) => id !== sectionID) });
const newMenuState = Object.assign({}, menuState, { sectionToSubsectionIndex: newIndex });
return Object.assign({}, state, { [menuID]: newMenuState });
}

View File

@@ -5,15 +5,31 @@ import { MenuService } from './menu.service';
import { cold, hot } from 'jasmine-marbles';
import { MenuID } from './initial-menus-state';
import { of as observableOf } from 'rxjs';
import {
ActivateMenuSectionAction,
AddMenuSectionAction,
CollapseMenuAction, CollapseMenuPreviewAction, DeactivateMenuSectionAction,
ExpandMenuAction, ExpandMenuPreviewAction, HideMenuAction,
RemoveMenuSectionAction, ShowMenuAction, ToggleActiveMenuSectionAction, ToggleMenuAction
} from './menu.actions';
fdescribe('MenuService', () => {
describe('MenuService', () => {
let service: MenuService;
let selectSpy;
const store = observableOf({}) as any;
const fakeMenu = { id: MenuID.ADMIN } as any;
const store = Object.assign(observableOf({}), {
dispatch: () => {/***/
}
}) as any;
const fakeMenu = {
id: MenuID.ADMIN,
collapsed: true,
visible: false,
previewCollapsed: true
} as any;
const visibleSection1 = {
id: 'section',
visible: true
visible: true,
active: false
};
const visibleSection2 = {
id: 'section_2',
@@ -48,6 +64,7 @@ fdescribe('MenuService', () => {
beforeEach(() => {
service = new MenuService(store);
selectSpy = spyOnProperty(ngrx, 'select');
spyOn(store, 'dispatch');
});
describe('getMenu', () => {
@@ -83,37 +100,297 @@ fdescribe('MenuService', () => {
};
});
});
it('should return only the visible top MenuSections', () => {
it('should return only the visible top MenuSections when mustBeVisible is true', () => {
const result = service.getMenuTopSections(MenuID.ADMIN);
const expected = cold('b', {
b: [visibleSection1, visibleSection2]
});
expect(result).toBeObservable(expected);
});
it('should return only the all top MenuSections when mustBeVisible is false', () => {
const result = service.getMenuTopSections(MenuID.ADMIN, false);
const expected = cold('b', {
b: [visibleSection1, visibleSection2, hiddenSection3]
});
expect(result).toBeObservable(expected);
})
});
// TODO finish this test
describe('getSubSectionsByParentID', () => {
describe('when the subsection list is not empty', () => {
beforeEach(() => {
spyOn(service, 'getMenuSection').and.returnValue(observableOf(visibleSection1));
selectSpy.and.callFake(() => {
return () => {
return () => hot('a', {
a: topSections
a: ['id1', 'id2']
}
);
};
});
});
it('should return only the visible top MenuSections', () => {
it('should return the MenuSections with the given parentID', () => {
const result = service.getMenuTopSections(MenuID.ADMIN);
const result = service.getSubSectionsByParentID(MenuID.ADMIN, 'fakeId');
const expected = cold('b', {
b: [visibleSection1, visibleSection2]
b: [visibleSection1, visibleSection1]
});
expect(result).toBeObservable(expected);
})
});
describe('when the subsection list is undefined', () => {
beforeEach(() => {
selectSpy.and.callFake(() => {
return () => {
return () => hot('a', {
a: undefined
}
);
};
});
});
it('should return an observable that emits nothing', () => {
const result = service.getSubSectionsByParentID(MenuID.ADMIN, 'fakeId');
const expected = cold('');
expect(result).toBeObservable(expected);
})
});
});
describe('hasSubSections', () => {
describe('when the subsection list is not empty', () => {
beforeEach(() => {
selectSpy.and.callFake(() => {
return () => {
return () => hot('a', {
a: ['id1', 'id2']
}
);
};
});
});
it('should return true', () => {
const result = service.hasSubSections(MenuID.ADMIN, 'fakeId');
const expected = cold('b', {
b: true
});
expect(result).toBeObservable(expected);
});
});
describe('when the subsection list is empty', () => {
beforeEach(() => {
selectSpy.and.callFake(() => {
return () => {
return () => hot('a', {
a: []
}
);
};
});
});
it('should return false', () => {
const result = service.hasSubSections(MenuID.ADMIN, 'fakeId');
const expected = cold('b', {
b: false
});
expect(result).toBeObservable(expected);
});
})
});
describe('getMenuSection', () => {
beforeEach(() => {
selectSpy.and.callFake(() => {
return () => {
return () => hot('a', {
a: hiddenSection3
}
);
};
});
});
it('should return false', () => {
const result = service.getMenuSection(MenuID.ADMIN, 'fakeId');
const expected = cold('b', {
b: hiddenSection3
});
expect(result).toBeObservable(expected);
});
});
describe('isMenuCollapsed', () => {
beforeEach(() => {
spyOn(service, 'getMenu').and.returnValue(observableOf(fakeMenu));
});
it('should return true when the menu is collapsed', () => {
const result = service.isMenuCollapsed(MenuID.ADMIN);
const expected = cold('(b|)', {
b: fakeMenu.collapsed
});
expect(result).toBeObservable(expected);
});
});
describe('isMenuPreviewCollapsed', () => {
beforeEach(() => {
spyOn(service, 'getMenu').and.returnValue(observableOf(fakeMenu));
});
it('should return true when the menu\'s preview is collapsed', () => {
const result = service.isMenuPreviewCollapsed(MenuID.ADMIN);
const expected = cold('(b|)', {
b: fakeMenu.previewCollapsed
});
expect(result).toBeObservable(expected);
});
});
describe('isMenuVisible', () => {
beforeEach(() => {
spyOn(service, 'getMenu').and.returnValue(observableOf(fakeMenu));
});
it('should return false when the menu is hidden', () => {
const result = service.isMenuVisible(MenuID.ADMIN);
const expected = cold('(b|)', {
b: fakeMenu.visible
});
expect(result).toBeObservable(expected);
});
});
describe('isSectionActive', () => {
beforeEach(() => {
spyOn(service, 'getMenuSection').and.returnValue(observableOf(visibleSection1));
});
it('should return false when the section is not active', () => {
const result = service.isSectionActive(MenuID.ADMIN, 'fakeID');
const expected = cold('(b|)', {
b: visibleSection1.active
});
expect(result).toBeObservable(expected);
});
});
describe('isSectionVisible', () => {
beforeEach(() => {
spyOn(service, 'getMenuSection').and.returnValue(observableOf(hiddenSection3));
});
it('should return false when the section is hidden', () => {
const result = service.isSectionVisible(MenuID.ADMIN, 'fakeID');
const expected = cold('(b|)', {
b: hiddenSection3.visible
});
expect(result).toBeObservable(expected);
});
});
describe('addSection', () => {
it('should dispatch an AddMenuSectionAction with the correct arguments', () => {
service.addSection(MenuID.ADMIN, visibleSection1 as any);
expect(store.dispatch).toHaveBeenCalledWith(new AddMenuSectionAction(MenuID.ADMIN, visibleSection1 as any));
});
});
describe('removeSection', () => {
it('should dispatch an RemoveMenuSectionAction with the correct arguments', () => {
service.removeSection(MenuID.ADMIN, 'fakeID');
expect(store.dispatch).toHaveBeenCalledWith(new RemoveMenuSectionAction(MenuID.ADMIN, 'fakeID'));
});
});
describe('expandMenu', () => {
it('should dispatch an ExpandMenuAction with the correct arguments', () => {
service.expandMenu(MenuID.ADMIN);
expect(store.dispatch).toHaveBeenCalledWith(new ExpandMenuAction(MenuID.ADMIN));
});
});
describe('collapseMenu', () => {
it('should dispatch an CollapseMenuAction with the correct arguments', () => {
service.collapseMenu(MenuID.ADMIN);
expect(store.dispatch).toHaveBeenCalledWith(new CollapseMenuAction(MenuID.ADMIN));
});
});
describe('expandMenuPreview', () => {
it('should dispatch an ExpandMenuPreviewAction with the correct arguments', () => {
service.expandMenuPreview(MenuID.ADMIN);
expect(store.dispatch).toHaveBeenCalledWith(new ExpandMenuPreviewAction(MenuID.ADMIN));
});
});
describe('collapseMenuPreview', () => {
it('should dispatch an CollapseMenuPreviewAction with the correct arguments', () => {
service.collapseMenuPreview(MenuID.ADMIN);
expect(store.dispatch).toHaveBeenCalledWith(new CollapseMenuPreviewAction(MenuID.ADMIN));
});
});
describe('toggleMenu', () => {
it('should dispatch an ToggleMenuAction with the correct arguments', () => {
service.toggleMenu(MenuID.ADMIN);
expect(store.dispatch).toHaveBeenCalledWith(new ToggleMenuAction(MenuID.ADMIN));
});
});
describe('showMenu', () => {
it('should dispatch an ShowMenuAction with the correct arguments', () => {
service.showMenu(MenuID.ADMIN);
expect(store.dispatch).toHaveBeenCalledWith(new ShowMenuAction(MenuID.ADMIN));
});
});
describe('hideMenu', () => {
it('should dispatch an HideMenuAction with the correct arguments', () => {
service.hideMenu(MenuID.ADMIN);
expect(store.dispatch).toHaveBeenCalledWith(new HideMenuAction(MenuID.ADMIN));
});
});
describe('toggleActiveSection', () => {
it('should dispatch an ToggleActiveMenuSectionAction with the correct arguments', () => {
service.toggleActiveSection(MenuID.ADMIN, 'fakeID');
expect(store.dispatch).toHaveBeenCalledWith(new ToggleActiveMenuSectionAction(MenuID.ADMIN, 'fakeID'));
});
});
describe('activateSection', () => {
it('should dispatch an ActivateMenuSectionAction with the correct arguments', () => {
service.activateSection(MenuID.ADMIN, 'fakeID');
expect(store.dispatch).toHaveBeenCalledWith(new ActivateMenuSectionAction(MenuID.ADMIN, 'fakeID'));
});
});
describe('deactivateSection', () => {
it('should dispatch an DeactivateMenuSectionAction with the correct arguments', () => {
service.deactivateSection(MenuID.ADMIN, 'fakeID');
expect(store.dispatch).toHaveBeenCalledWith(new DeactivateMenuSectionAction(MenuID.ADMIN, 'fakeID'));
});
});
});

View File

@@ -93,4 +93,8 @@ export class AuthServiceStub {
public storeToken(token: AuthTokenInfo) {
return;
}
isAuthenticated() {
return observableOf(true);
}
}

View File

@@ -0,0 +1,12 @@
import { ComponentFactory } from '@angular/core';
import { create } from 'domain';
export class ComponentInjectorStub {
resolveComponentFactory(): ComponentFactory<any> {
return {
create() {
return { hostView: {}, viewContainerParent: {}, }
}
} as any;
}
}

View File

@@ -0,0 +1,8 @@
import { Observable } from 'rxjs/internal/Observable';
import { of as observableOf } from 'rxjs';
export class CSSVariableServiceStub {
getVariable(name: string): Observable<string> {
return observableOf('500px');
}
}

View File

@@ -0,0 +1,93 @@
import { MenuID } from '../menu/initial-menus-state';
import { of as observableOf } from 'rxjs';
import { Observable } from 'rxjs/internal/Observable';
import { MenuSection } from '../menu/menu.reducer';
export class MenuServiceStub {
visibleSection1 = {
id: 'section',
visible: true,
active: false
} as any;
visibleSection2 = {
id: 'section_2',
visible: true
} as any;
hiddenSection3 = {
id: 'section_3',
visible: false
} as any;
subSection4 = {
id: 'section_4',
visible: true,
parentID: 'section1'
} as any;
toggleMenu(): void { /***/
};
expandMenu(): void { /***/
};
collapseMenu(): void { /***/
};
showMenu(): void { /***/
};
hideMenu(): void { /***/
};
expandMenuPreview(): void { /***/
};
collapseMenuPreview(): void { /***/
};
toggleActiveSection(): void { /***/
};
activateSection(): void { /***/
};
deactivateSection(): void { /***/
};
addSection(): void { /***/
};
removeSection(): void { /***/
};
isMenuVisible(id: MenuID): Observable<boolean> {
return observableOf(true)
};
isMenuCollapsed(id: MenuID): Observable<boolean> {
return observableOf(false)
};
isMenuPreviewCollapsed(id: MenuID): Observable<boolean> {
return observableOf(true)
};
hasSubSections(id: MenuID, sectionID: string): Observable<boolean> {
return observableOf(true)
};
getMenuTopSections(id: MenuID): Observable<MenuSection[]> {
return observableOf([this.visibleSection1, this.visibleSection2])
};
getSubSectionsByParentID(id: MenuID): Observable<MenuSection[]> {
return observableOf([this.subSection4])
};
isSectionActive(id: MenuID, sectionID: string): Observable<boolean> {
return observableOf(true)
};
isSectionVisible(id: MenuID, sectionID: string): Observable<boolean> {
return observableOf(true)
};
}

View File

@@ -0,0 +1,10 @@
import { Directive, Input } from '@angular/core';
/* tslint:disable:directive-class-suffix */
@Directive({
// tslint:disable-next-line:directive-selector
selector: '[ngComponentOutlet]',
})
export class NgComponentOutletDirectiveStub {
@Input() ngComponentOutlet: any;
}

View File

@@ -0,0 +1,10 @@
import { Directive, Input } from '@angular/core';
/* tslint:disable:directive-class-suffix */
@Directive({
// tslint:disable-next-line:directive-selector
selector: '[routerLink]',
})
export class RouterLinkDirectiveStub {
@Input() routerLink: any;
}

View File

@@ -1,5 +1,7 @@
import { NgModule } from '@angular/core';
import { QueryParamsDirectiveStub } from './query-params-directive-stub';
import { RouterLinkDirectiveStub } from './router-link-directive-stub';
import { NgComponentOutletDirectiveStub } from './ng-component-outlet-directive-stub';
/**
* This module isn't used. It serves to prevent the AoT compiler
@@ -9,7 +11,9 @@ import { QueryParamsDirectiveStub } from './query-params-directive-stub';
*/
@NgModule({
declarations: [
QueryParamsDirectiveStub
QueryParamsDirectiveStub,
RouterLinkDirectiveStub,
NgComponentOutletDirectiveStub
]
})
export class TestModule {}