mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 18:14:17 +00:00
1
.gitignore
vendored
1
.gitignore
vendored
@@ -6,6 +6,7 @@
|
|||||||
npm-debug.log
|
npm-debug.log
|
||||||
|
|
||||||
/dist/
|
/dist/
|
||||||
|
/coverage/
|
||||||
|
|
||||||
.idea
|
.idea
|
||||||
*.ngfactory.ts
|
*.ngfactory.ts
|
||||||
|
@@ -106,6 +106,7 @@
|
|||||||
"@types/body-parser": "0.0.33",
|
"@types/body-parser": "0.0.33",
|
||||||
"@types/compression": "0.0.33",
|
"@types/compression": "0.0.33",
|
||||||
"@types/cookie-parser": "1.3.30",
|
"@types/cookie-parser": "1.3.30",
|
||||||
|
"@types/deep-freeze": "0.0.29",
|
||||||
"@types/express": "4.0.34",
|
"@types/express": "4.0.34",
|
||||||
"@types/express-serve-static-core": "4.0.39",
|
"@types/express-serve-static-core": "4.0.39",
|
||||||
"@types/hammerjs": "2.0.33",
|
"@types/hammerjs": "2.0.33",
|
||||||
@@ -126,6 +127,7 @@
|
|||||||
"cookie-parser": "1.4.3",
|
"cookie-parser": "1.4.3",
|
||||||
"copy-webpack-plugin": "4.0.1",
|
"copy-webpack-plugin": "4.0.1",
|
||||||
"css-loader": "^0.26.0",
|
"css-loader": "^0.26.0",
|
||||||
|
"deep-freeze": "0.0.1",
|
||||||
"html-webpack-plugin": "^2.21.0",
|
"html-webpack-plugin": "^2.21.0",
|
||||||
"imports-loader": "0.7.0",
|
"imports-loader": "0.7.0",
|
||||||
"istanbul-instrumenter-loader": "^0.2.0",
|
"istanbul-instrumenter-loader": "^0.2.0",
|
||||||
|
@@ -10,15 +10,16 @@ import {
|
|||||||
DebugElement
|
DebugElement
|
||||||
} from "@angular/core";
|
} from "@angular/core";
|
||||||
import { By } from '@angular/platform-browser';
|
import { By } from '@angular/platform-browser';
|
||||||
import { TranslateModule } from "ng2-translate";
|
import { TranslateModule, TranslateLoader } from "ng2-translate";
|
||||||
import { NgbCollapseModule } from '@ng-bootstrap/ng-bootstrap';
|
import { Store, StoreModule } from "@ngrx/store";
|
||||||
import { Store } from "@ngrx/store";
|
|
||||||
|
|
||||||
// Load the implementations that should be tested
|
// Load the implementations that should be tested
|
||||||
import { AppComponent } from './app.component';
|
import { AppComponent } from './app.component';
|
||||||
import { HeaderComponent } from './header/header.component';
|
|
||||||
|
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { HostWindowState } from "./shared/host-window.reducer";
|
||||||
|
import { HostWindowResizeAction } from "./shared/host-window.actions";
|
||||||
|
import { MockTranslateLoader } from "./shared/testing/mock-translate-loader";
|
||||||
|
|
||||||
let comp: AppComponent;
|
let comp: AppComponent;
|
||||||
let fixture: ComponentFixture<AppComponent>;
|
let fixture: ComponentFixture<AppComponent>;
|
||||||
@@ -30,14 +31,13 @@ describe('App component', () => {
|
|||||||
// async beforeEach
|
// async beforeEach
|
||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
return TestBed.configureTestingModule({
|
return TestBed.configureTestingModule({
|
||||||
imports: [ CommonModule, TranslateModule.forRoot(), NgbCollapseModule.forRoot()],
|
imports: [CommonModule, StoreModule.provideStore({}), TranslateModule.forRoot({
|
||||||
declarations: [ AppComponent, HeaderComponent ], // declare the test component
|
provide: TranslateLoader,
|
||||||
|
useClass: MockTranslateLoader
|
||||||
|
})],
|
||||||
|
declarations: [AppComponent], // declare the test component
|
||||||
providers: [
|
providers: [
|
||||||
AppComponent,
|
AppComponent
|
||||||
{
|
|
||||||
provide: Store,
|
|
||||||
useClass: class { dispatch = jasmine.createSpy('dispatch') }
|
|
||||||
}
|
|
||||||
],
|
],
|
||||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||||
})
|
})
|
||||||
@@ -47,7 +47,7 @@ describe('App component', () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
fixture = TestBed.createComponent(AppComponent);
|
fixture = TestBed.createComponent(AppComponent);
|
||||||
|
|
||||||
comp = fixture.componentInstance; // BannerComponent test instance
|
comp = fixture.componentInstance; // component test instance
|
||||||
|
|
||||||
// query for the title <p> by CSS element selector
|
// query for the title <p> by CSS element selector
|
||||||
de = fixture.debugElement.query(By.css('p'));
|
de = fixture.debugElement.query(By.css('p'));
|
||||||
@@ -58,4 +58,24 @@ describe('App component', () => {
|
|||||||
// Perform test using fixture and service
|
// Perform test using fixture and service
|
||||||
expect(app).toBeTruthy();
|
expect(app).toBeTruthy();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
describe("when the window is resized", () => {
|
||||||
|
let width: number;
|
||||||
|
let height: number;
|
||||||
|
let store: Store<HostWindowState>;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
store = fixture.debugElement.injector.get(Store);
|
||||||
|
spyOn(store, 'dispatch');
|
||||||
|
|
||||||
|
window.dispatchEvent(new Event('resize'));
|
||||||
|
width = window.innerWidth;
|
||||||
|
height = window.innerHeight;
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should dispatch a HostWindowResizeAction with the width and height of the window as its payload", () => {
|
||||||
|
expect(store.dispatch).toHaveBeenCalledWith(new HostWindowResizeAction(width, height));
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@@ -8,7 +8,7 @@ import {
|
|||||||
import { TranslateService } from "ng2-translate";
|
import { TranslateService } from "ng2-translate";
|
||||||
import { HostWindowState } from "./shared/host-window.reducer";
|
import { HostWindowState } from "./shared/host-window.reducer";
|
||||||
import { Store } from "@ngrx/store";
|
import { Store } from "@ngrx/store";
|
||||||
import { HostWindowActions } from "./shared/host-window.actions";
|
import { HostWindowResizeAction } from "./shared/host-window.actions";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
changeDetection: ChangeDetectionStrategy.Default,
|
changeDetection: ChangeDetectionStrategy.Default,
|
||||||
@@ -52,7 +52,7 @@ export class AppComponent implements OnDestroy, OnInit {
|
|||||||
@HostListener('window:resize', ['$event'])
|
@HostListener('window:resize', ['$event'])
|
||||||
private onResize(event): void {
|
private onResize(event): void {
|
||||||
this.store.dispatch(
|
this.store.dispatch(
|
||||||
HostWindowActions.resize(event.target.innerWidth, event.target.innerHeight)
|
new HostWindowResizeAction(event.target.innerWidth, event.target.innerHeight)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,24 +1,43 @@
|
|||||||
import { Action } from "@ngrx/store";
|
import { Action } from "@ngrx/store";
|
||||||
|
import { type } from "../shared/ngrx/type";
|
||||||
|
|
||||||
export class HeaderActions {
|
/**
|
||||||
static COLLAPSE = 'dspace/header/COLLAPSE';
|
* For each action type in an action group, make a simple
|
||||||
static collapse(): Action {
|
* enum object for all of this group's action types.
|
||||||
return {
|
*
|
||||||
type: HeaderActions.COLLAPSE
|
* The 'type' utility function coerces strings into string
|
||||||
}
|
* literal types and runs a simple check to guarantee all
|
||||||
|
* action types in the application are unique.
|
||||||
|
*/
|
||||||
|
export const HeaderActionTypes = {
|
||||||
|
COLLAPSE: type('dspace/header/COLLAPSE'),
|
||||||
|
EXPAND: type('dspace/header/EXPAND'),
|
||||||
|
TOGGLE: type('dspace/header/TOGGLE')
|
||||||
|
};
|
||||||
|
|
||||||
|
export class HeaderCollapseAction implements Action {
|
||||||
|
type = HeaderActionTypes.COLLAPSE;
|
||||||
|
|
||||||
|
constructor() {}
|
||||||
}
|
}
|
||||||
|
|
||||||
static EXPAND = 'dspace/header/EXPAND';
|
export class HeaderExpandAction implements Action {
|
||||||
static expand(): Action {
|
type = HeaderActionTypes.EXPAND;
|
||||||
return {
|
|
||||||
type: HeaderActions.EXPAND
|
constructor() {}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static TOGGLE = 'dspace/header/TOGGLE';
|
export class HeaderToggleAction implements Action {
|
||||||
static toggle(): Action {
|
type = HeaderActionTypes.TOGGLE;
|
||||||
return {
|
|
||||||
type: HeaderActions.TOGGLE
|
constructor() {}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Export a type alias of all actions in this action group
|
||||||
|
* so that reducers can easily compose action types
|
||||||
|
*/
|
||||||
|
export type HeaderAction
|
||||||
|
= HeaderCollapseAction
|
||||||
|
| HeaderExpandAction
|
||||||
|
| HeaderToggleAction
|
||||||
|
81
src/app/header/header.component.spec.ts
Normal file
81
src/app/header/header.component.spec.ts
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
import { ComponentFixture, TestBed, async } from '@angular/core/testing';
|
||||||
|
import { By } from '@angular/platform-browser';
|
||||||
|
import { HeaderComponent } from "./header.component";
|
||||||
|
import { Store, StoreModule } from "@ngrx/store";
|
||||||
|
import { HeaderState } from "./header.reducer";
|
||||||
|
import Spy = jasmine.Spy;
|
||||||
|
import { HeaderToggleAction } from "./header.actions";
|
||||||
|
import { TranslateModule } from "ng2-translate";
|
||||||
|
import { NgbCollapseModule } from "@ng-bootstrap/ng-bootstrap";
|
||||||
|
import { Observable } from "rxjs";
|
||||||
|
|
||||||
|
let comp: HeaderComponent;
|
||||||
|
let fixture: ComponentFixture<HeaderComponent>;
|
||||||
|
let store: Store<HeaderState>;
|
||||||
|
|
||||||
|
describe('HeaderComponent', () => {
|
||||||
|
|
||||||
|
// async beforeEach
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [ StoreModule.provideStore({}), TranslateModule.forRoot(), NgbCollapseModule.forRoot() ],
|
||||||
|
declarations: [ HeaderComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents(); // compile template and css
|
||||||
|
}));
|
||||||
|
|
||||||
|
// synchronous beforeEach
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(HeaderComponent);
|
||||||
|
|
||||||
|
comp = fixture.componentInstance;
|
||||||
|
|
||||||
|
|
||||||
|
store = fixture.debugElement.injector.get(Store);
|
||||||
|
spyOn(store, 'dispatch');
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the toggle button is clicked', () => {
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
let navbarToggler = fixture.debugElement.query(By.css('.navbar-toggler'));
|
||||||
|
navbarToggler.triggerEventHandler('click', null);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should dispatch a HeaderToggleAction", () => {
|
||||||
|
expect(store.dispatch).toHaveBeenCalledWith(new HeaderToggleAction());
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when navCollapsed in the store is true", () => {
|
||||||
|
let menu: HTMLElement;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
menu = fixture.debugElement.query(By.css('#collapsingNav')).nativeElement;
|
||||||
|
spyOn(store, 'select').and.returnValue(Observable.of({ navCollapsed: true }));
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should close the menu", () => {
|
||||||
|
expect(menu.classList).not.toContain('in');
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when navCollapsed in the store is false", () => {
|
||||||
|
let menu: HTMLElement;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
menu = fixture.debugElement.query(By.css('#collapsingNav')).nativeElement;
|
||||||
|
spyOn(store, 'select').and.returnValue(Observable.of({ navCollapsed: false }));
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should open the menu", () => {
|
||||||
|
expect(menu.classList).toContain('in');
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
@@ -1,9 +1,8 @@
|
|||||||
import { Component, OnInit } from "@angular/core";
|
import { Component, OnInit } from "@angular/core";
|
||||||
import { Store } from "@ngrx/store";
|
import { Store } from "@ngrx/store";
|
||||||
import { HeaderState } from "./header.reducer";
|
import { HeaderState } from "./header.reducer";
|
||||||
import { HeaderActions } from "./header.actions";
|
|
||||||
import { Observable } from "rxjs";
|
import { Observable } from "rxjs";
|
||||||
import 'rxjs/add/operator/filter';
|
import { HeaderToggleAction } from "./header.actions";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-header',
|
selector: 'ds-header',
|
||||||
@@ -24,16 +23,8 @@ export class HeaderComponent implements OnInit {
|
|||||||
.map(({ navCollapsed }: HeaderState) => navCollapsed);
|
.map(({ navCollapsed }: HeaderState) => navCollapsed);
|
||||||
}
|
}
|
||||||
|
|
||||||
private collapse(): void {
|
|
||||||
this.store.dispatch(HeaderActions.collapse());
|
|
||||||
}
|
|
||||||
|
|
||||||
private expand(): void {
|
|
||||||
this.store.dispatch(HeaderActions.expand());
|
|
||||||
}
|
|
||||||
|
|
||||||
public toggle(): void {
|
public toggle(): void {
|
||||||
this.store.dispatch(HeaderActions.toggle());
|
this.store.dispatch(new HeaderToggleAction());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
53
src/app/header/header.effects.spec.ts
Normal file
53
src/app/header/header.effects.spec.ts
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import { TestBed, inject } from "@angular/core/testing";
|
||||||
|
import { EffectsTestingModule, EffectsRunner } from '@ngrx/effects/testing';
|
||||||
|
import { HeaderEffects } from "./header.effects";
|
||||||
|
import { HeaderCollapseAction } from "./header.actions";
|
||||||
|
import { HostWindowResizeAction } from "../shared/host-window.actions";
|
||||||
|
import { routerActions } from "@ngrx/router-store";
|
||||||
|
|
||||||
|
describe('HeaderEffects', () => {
|
||||||
|
beforeEach(() => TestBed.configureTestingModule({
|
||||||
|
imports: [
|
||||||
|
EffectsTestingModule
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
HeaderEffects
|
||||||
|
]
|
||||||
|
}));
|
||||||
|
|
||||||
|
let runner: EffectsRunner;
|
||||||
|
let headerEffects: HeaderEffects;
|
||||||
|
|
||||||
|
beforeEach(inject([
|
||||||
|
EffectsRunner, HeaderEffects
|
||||||
|
],
|
||||||
|
(_runner, _headerEffects) => {
|
||||||
|
runner = _runner;
|
||||||
|
headerEffects = _headerEffects;
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
describe('resize$', () => {
|
||||||
|
|
||||||
|
it('should return a COLLAPSE action in response to a RESIZE action', () => {
|
||||||
|
runner.queue(new HostWindowResizeAction(800,600));
|
||||||
|
|
||||||
|
headerEffects.resize$.subscribe(result => {
|
||||||
|
expect(result).toEqual(new HeaderCollapseAction());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('routeChange$', () => {
|
||||||
|
|
||||||
|
it('should return a COLLAPSE action in response to an UPDATE_LOCATION action', () => {
|
||||||
|
runner.queue({ type: routerActions.UPDATE_LOCATION });
|
||||||
|
|
||||||
|
headerEffects.resize$.subscribe(result => {
|
||||||
|
expect(result).toEqual(new HeaderCollapseAction());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
@@ -1,8 +1,8 @@
|
|||||||
import { Injectable } from "@angular/core";
|
import { Injectable } from "@angular/core";
|
||||||
import { Effect, Actions } from '@ngrx/effects'
|
import { Effect, Actions } from '@ngrx/effects'
|
||||||
import { HeaderActions } from "./header.actions";
|
import { HostWindowActionTypes } from "../shared/host-window.actions";
|
||||||
import { HostWindowActions } from "../shared/host-window.actions";
|
|
||||||
import { routerActions } from "@ngrx/router-store";
|
import { routerActions } from "@ngrx/router-store";
|
||||||
|
import { HeaderCollapseAction } from "./header.actions";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class HeaderEffects {
|
export class HeaderEffects {
|
||||||
@@ -12,10 +12,10 @@ export class HeaderEffects {
|
|||||||
) { }
|
) { }
|
||||||
|
|
||||||
@Effect() resize$ = this.actions$
|
@Effect() resize$ = this.actions$
|
||||||
.ofType(HostWindowActions.RESIZE)
|
.ofType(HostWindowActionTypes.RESIZE)
|
||||||
.map(() => HeaderActions.collapse());
|
.map(() => new HeaderCollapseAction());
|
||||||
|
|
||||||
@Effect() routeChange$ = this.actions$
|
@Effect() routeChange$ = this.actions$
|
||||||
.ofType(routerActions.UPDATE_LOCATION)
|
.ofType(routerActions.UPDATE_LOCATION)
|
||||||
.map(() => HeaderActions.collapse());
|
.map(() => new HeaderCollapseAction());
|
||||||
}
|
}
|
||||||
|
89
src/app/header/header.reducer.spec.ts
Normal file
89
src/app/header/header.reducer.spec.ts
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
import * as deepFreeze from "deep-freeze";
|
||||||
|
|
||||||
|
import { headerReducer } from "./header.reducer";
|
||||||
|
import {
|
||||||
|
HeaderCollapseAction,
|
||||||
|
HeaderExpandAction,
|
||||||
|
HeaderToggleAction
|
||||||
|
} from "./header.actions";
|
||||||
|
|
||||||
|
class NullAction extends HeaderCollapseAction {
|
||||||
|
type = null;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("headerReducer", () => {
|
||||||
|
it("should return the current state when no valid actions have been made", () => {
|
||||||
|
const state = { navCollapsed: false };
|
||||||
|
const action = new NullAction();
|
||||||
|
const newState = headerReducer(state, action);
|
||||||
|
|
||||||
|
expect(newState).toEqual(state);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should start with navCollapsed = true", () => {
|
||||||
|
const action = new NullAction();
|
||||||
|
const initialState = headerReducer(undefined, action);
|
||||||
|
|
||||||
|
// The navigation starts collapsed
|
||||||
|
expect(initialState.navCollapsed).toEqual(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should set navCollapsed to true in response to the COLLAPSE action", () => {
|
||||||
|
const state = { navCollapsed: false };
|
||||||
|
const action = new HeaderCollapseAction();
|
||||||
|
const newState = headerReducer(state, action);
|
||||||
|
|
||||||
|
expect(newState.navCollapsed).toEqual(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should perform the COLLAPSE action without affecting the previous state", () => {
|
||||||
|
const state = { navCollapsed: false };
|
||||||
|
deepFreeze(state);
|
||||||
|
|
||||||
|
const action = new HeaderCollapseAction();
|
||||||
|
headerReducer(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 navCollapsed to false in response to the EXPAND action", () => {
|
||||||
|
const state = { navCollapsed: true };
|
||||||
|
const action = new HeaderExpandAction();
|
||||||
|
const newState = headerReducer(state, action);
|
||||||
|
|
||||||
|
expect(newState.navCollapsed).toEqual(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should perform the EXPAND action without affecting the previous state", () => {
|
||||||
|
const state = { navCollapsed: true };
|
||||||
|
deepFreeze(state);
|
||||||
|
|
||||||
|
const action = new HeaderExpandAction();
|
||||||
|
headerReducer(state, action);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should flip the value of navCollapsed in response to the TOGGLE action", () => {
|
||||||
|
const state1 = { navCollapsed: true };
|
||||||
|
const action = new HeaderToggleAction();
|
||||||
|
|
||||||
|
const state2 = headerReducer(state1, action);
|
||||||
|
const state3 = headerReducer(state2, action);
|
||||||
|
|
||||||
|
expect(state2.navCollapsed).toEqual(false);
|
||||||
|
expect(state3.navCollapsed).toEqual(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should perform the TOGGLE action without affecting the previous state", () => {
|
||||||
|
const state = { navCollapsed: true };
|
||||||
|
deepFreeze(state);
|
||||||
|
|
||||||
|
const action = new HeaderToggleAction();
|
||||||
|
headerReducer(state, action);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
@@ -1,5 +1,4 @@
|
|||||||
import { Action } from "@ngrx/store";
|
import { HeaderAction, HeaderActionTypes } from "./header.actions";
|
||||||
import { HeaderActions } from "./header.actions";
|
|
||||||
|
|
||||||
export interface HeaderState {
|
export interface HeaderState {
|
||||||
navCollapsed: boolean;
|
navCollapsed: boolean;
|
||||||
@@ -9,23 +8,23 @@ const initialState: HeaderState = {
|
|||||||
navCollapsed: true
|
navCollapsed: true
|
||||||
};
|
};
|
||||||
|
|
||||||
export const headerReducer = (state = initialState, action: Action): HeaderState => {
|
export const headerReducer = (state = initialState, action: HeaderAction): HeaderState => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
|
|
||||||
case HeaderActions.COLLAPSE: {
|
case HeaderActionTypes.COLLAPSE: {
|
||||||
return Object.assign({}, state, {
|
return Object.assign({}, state, {
|
||||||
navCollapsed: true
|
navCollapsed: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
case HeaderActions.EXPAND: {
|
case HeaderActionTypes.EXPAND: {
|
||||||
return Object.assign({}, state, {
|
return Object.assign({}, state, {
|
||||||
navCollapsed: false
|
navCollapsed: false
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case HeaderActions.TOGGLE: {
|
case HeaderActionTypes.TOGGLE: {
|
||||||
return Object.assign({}, state, {
|
return Object.assign({}, state, {
|
||||||
navCollapsed: !state.navCollapsed
|
navCollapsed: !state.navCollapsed
|
||||||
});
|
});
|
||||||
|
@@ -1,14 +1,21 @@
|
|||||||
import { Action } from "@ngrx/store";
|
import { Action } from "@ngrx/store";
|
||||||
|
import { type } from "./ngrx/type";
|
||||||
|
|
||||||
export class HostWindowActions {
|
export const HostWindowActionTypes = {
|
||||||
static RESIZE = 'dspace/host-window/RESIZE';
|
RESIZE: type('dspace/host-window/RESIZE')
|
||||||
static resize(newWidth: number, newHeight: number): Action {
|
};
|
||||||
return {
|
|
||||||
type: HostWindowActions.RESIZE,
|
export class HostWindowResizeAction implements Action {
|
||||||
|
type = HostWindowActionTypes.RESIZE;
|
||||||
payload: {
|
payload: {
|
||||||
width: newWidth,
|
width: number;
|
||||||
height: newHeight
|
height: number;
|
||||||
}
|
};
|
||||||
}
|
|
||||||
|
constructor(width: number, height: number) {
|
||||||
|
this.payload = { width, height }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type HostWindowAction
|
||||||
|
= HostWindowResizeAction;
|
||||||
|
48
src/app/shared/host-window.reducer.spec.ts
Normal file
48
src/app/shared/host-window.reducer.spec.ts
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import * as deepFreeze from "deep-freeze";
|
||||||
|
import { hostWindowReducer } from "./host-window.reducer";
|
||||||
|
import { HostWindowResizeAction } from "./host-window.actions";
|
||||||
|
|
||||||
|
class NullAction extends HostWindowResizeAction {
|
||||||
|
type = null;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super(0,0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('hostWindowReducer', () => {
|
||||||
|
|
||||||
|
it("should return the current state when no valid actions have been made", () => {
|
||||||
|
const state = { width: 800, height: 600 };
|
||||||
|
const action = new NullAction();
|
||||||
|
const newState = hostWindowReducer(state, action);
|
||||||
|
|
||||||
|
expect(newState).toEqual(state);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should start with width = null and height = null", () => {
|
||||||
|
const action = new NullAction();
|
||||||
|
const initialState = hostWindowReducer(undefined, action);
|
||||||
|
|
||||||
|
expect(initialState.width).toEqual(null);
|
||||||
|
expect(initialState.height).toEqual(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should update the width and height in the state in response to a RESIZE action", () => {
|
||||||
|
const state = { width: 800, height: 600 };
|
||||||
|
const action = new HostWindowResizeAction(1024, 768);
|
||||||
|
const newState = hostWindowReducer(state, action);
|
||||||
|
|
||||||
|
expect(newState.width).toEqual(1024);
|
||||||
|
expect(newState.height).toEqual(768);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should perform the RESIZE action without affecting the previous state", () => {
|
||||||
|
const state = { width: 800, height: 600 };
|
||||||
|
deepFreeze(state);
|
||||||
|
|
||||||
|
const action = new HostWindowResizeAction(1024, 768);
|
||||||
|
hostWindowReducer(state, action);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
@@ -1,5 +1,4 @@
|
|||||||
import { Action } from "@ngrx/store";
|
import { HostWindowAction, HostWindowActionTypes } from "./host-window.actions";
|
||||||
import { HostWindowActions } from "./host-window.actions";
|
|
||||||
|
|
||||||
export interface HostWindowState {
|
export interface HostWindowState {
|
||||||
width: number;
|
width: number;
|
||||||
@@ -11,10 +10,10 @@ const initialState: HostWindowState = {
|
|||||||
height: null
|
height: null
|
||||||
};
|
};
|
||||||
|
|
||||||
export const hostWindowReducer = (state = initialState, action: Action): HostWindowState => {
|
export const hostWindowReducer = (state = initialState, action: HostWindowAction): HostWindowState => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
|
|
||||||
case HostWindowActions.RESIZE: {
|
case HostWindowActionTypes.RESIZE: {
|
||||||
return Object.assign({}, state, action.payload);
|
return Object.assign({}, state, action.payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
24
src/app/shared/ngrx/type.ts
Normal file
24
src/app/shared/ngrx/type.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
|
||||||
|
/**
|
||||||
|
* Based on
|
||||||
|
* https://github.com/ngrx/example-app/blob/master/src/app/util.ts
|
||||||
|
*
|
||||||
|
* This function coerces a string into a string literal type.
|
||||||
|
* Using tagged union types in TypeScript 2.0, this enables
|
||||||
|
* powerful typechecking of our reducers.
|
||||||
|
*
|
||||||
|
* Since every action label passes through this function it
|
||||||
|
* is a good place to ensure all of our action labels
|
||||||
|
* are unique.
|
||||||
|
*/
|
||||||
|
|
||||||
|
let typeCache: { [label: string]: boolean } = {};
|
||||||
|
export function type<T>(label: T | ''): T {
|
||||||
|
if (typeCache[<string>label]) {
|
||||||
|
throw new Error(`Action type "${label}" is not unique"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
typeCache[<string>label] = true;
|
||||||
|
|
||||||
|
return <T>label;
|
||||||
|
}
|
8
src/app/shared/testing/mock-translate-loader.ts
Normal file
8
src/app/shared/testing/mock-translate-loader.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { TranslateLoader } from "ng2-translate";
|
||||||
|
import { Observable } from "rxjs";
|
||||||
|
|
||||||
|
export class MockTranslateLoader implements TranslateLoader {
|
||||||
|
getTranslation(lang: string): Observable<any> {
|
||||||
|
return Observable.of({});
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user