Skip to content

Commit

Permalink
feat(example): add logout confirmation (#1287)
Browse files Browse the repository at this point in the history
Closes #1271
  • Loading branch information
koumatsumoto authored and brandonroberts committed Aug 28, 2018
1 parent 466e2cd commit ba8d300
Show file tree
Hide file tree
Showing 9 changed files with 215 additions and 5 deletions.
14 changes: 13 additions & 1 deletion projects/example-app/src/app/auth/actions/auth.actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ export enum AuthActionTypes {
LoginSuccess = '[Auth] Login Success',
LoginFailure = '[Auth] Login Failure',
LoginRedirect = '[Auth] Login Redirect',
LogoutConfirmation = '[Auth] Logout Confirmation',
LogoutConfirmationDismiss = '[Auth] Logout Confirmation Dismiss',
}

export class Login implements Action {
Expand Down Expand Up @@ -35,9 +37,19 @@ export class Logout implements Action {
readonly type = AuthActionTypes.Logout;
}

export class LogoutConfirmation implements Action {
readonly type = AuthActionTypes.LogoutConfirmation;
}

export class LogoutConfirmationDismiss implements Action {
readonly type = AuthActionTypes.LogoutConfirmationDismiss;
}

export type AuthActionsUnion =
| Login
| LoginSuccess
| LoginFailure
| LoginRedirect
| Logout;
| Logout
| LogoutConfirmation
| LogoutConfirmationDismiss;
8 changes: 7 additions & 1 deletion projects/example-app/src/app/auth/auth.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,18 @@ import { StoreModule } from '@ngrx/store';
import { EffectsModule } from '@ngrx/effects';
import { LoginPageComponent } from './containers/login-page.component';
import { LoginFormComponent } from './components/login-form.component';
import { LogoutConfirmationDialogComponent } from './components/logout-confirmation-dialog.component';

import { AuthEffects } from './effects/auth.effects';
import { reducers } from './reducers';
import { MaterialModule } from '../material';
import { AuthRoutingModule } from './auth-routing.module';

export const COMPONENTS = [LoginPageComponent, LoginFormComponent];
export const COMPONENTS = [
LoginPageComponent,
LoginFormComponent,
LogoutConfirmationDialogComponent,
];

@NgModule({
imports: [
Expand All @@ -23,5 +28,6 @@ export const COMPONENTS = [LoginPageComponent, LoginFormComponent];
EffectsModule.forFeature([AuthEffects]),
],
declarations: COMPONENTS,
entryComponents: [LogoutConfirmationDialogComponent],
})
export class AuthModule {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Logout Confirmation Dialog should compile 1`] = `
<ng-component>
<h2
_ngcontent-c0=""
class="mat-dialog-title"
id="mat-dialog-title-0"
mat-dialog-title=""
>
Logout
</h2><mat-dialog-content
_ngcontent-c0=""
class="mat-dialog-content"
>
Are you sure you want to logout?
</mat-dialog-content><mat-dialog-actions
_ngcontent-c0=""
class="mat-dialog-actions"
>
<button
_ngcontent-c0=""
aria-label="Close dialog"
class="mat-button"
mat-button=""
ng-reflect-dialog-result="false"
type="button"
>
<span
class="mat-button-wrapper"
>
Cancel
</span>
<div
class="mat-button-ripple mat-ripple"
matripple=""
ng-reflect-centered="false"
ng-reflect-disabled="false"
ng-reflect-trigger="[object HTMLButtonElement]"
/>
<div
class="mat-button-focus-overlay"
/>
</button>
<button
_ngcontent-c0=""
aria-label="Close dialog"
class="mat-button"
mat-button=""
ng-reflect-dialog-result="true"
type="button"
>
<span
class="mat-button-wrapper"
>
OK
</span>
<div
class="mat-button-ripple mat-ripple"
matripple=""
ng-reflect-centered="false"
ng-reflect-disabled="false"
ng-reflect-trigger="[object HTMLButtonElement]"
/>
<div
class="mat-button-focus-overlay"
/>
</button>
</mat-dialog-actions>
</ng-component>
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MatButtonModule, MatDialogModule } from '@angular/material';
import { LogoutConfirmationDialogComponent } from './logout-confirmation-dialog.component';

describe('Logout Confirmation Dialog', () => {
let fixture: ComponentFixture<LogoutConfirmationDialogComponent>;

beforeEach(() => {
TestBed.configureTestingModule({
imports: [MatButtonModule, MatDialogModule],
declarations: [LogoutConfirmationDialogComponent],
});

fixture = TestBed.createComponent(LogoutConfirmationDialogComponent);
});

it('should compile', () => {
fixture.detectChanges();

expect(fixture).toMatchSnapshot();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { Component } from '@angular/core';

/**
* The dialog will close with true if user clicks the ok button,
* otherwise it will close with undefined.
*/
@Component({
template: `
<h2 mat-dialog-title>Logout</h2>
<mat-dialog-content>Are you sure you want to logout?</mat-dialog-content>
<mat-dialog-actions>
<button mat-button [mat-dialog-close]="false">Cancel</button>
<button mat-button [mat-dialog-close]="true">OK</button>
</mat-dialog-actions>
`,
styles: [
`
:host {
display: block;
width: 100%;
max-width: 300px;
}
mat-dialog-actions {
display: flex;
justify-content: flex-end;
}
[mat-button] {
padding: 0;
}
`,
],
})
export class LogoutConfirmationDialogComponent {}
43 changes: 42 additions & 1 deletion projects/example-app/src/app/auth/effects/auth.effects.spec.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import { TestBed } from '@angular/core/testing';
import { MatDialog } from '@angular/material';
import { Router } from '@angular/router';
import { Actions } from '@ngrx/effects';
import { provideMockActions } from '@ngrx/effects/testing';
import { cold, hot } from 'jasmine-marbles';
import { empty, Observable } from 'rxjs';
import { empty, Observable, of } from 'rxjs';
import {
Login,
LoginFailure,
LoginRedirect,
LoginSuccess,
Logout,
LogoutConfirmation,
LogoutConfirmationDismiss,
} from '../actions/auth.actions';
import { Authenticate, User } from '../models/user';
import { AuthService } from '../services/auth.service';
Expand All @@ -20,6 +23,7 @@ describe('AuthEffects', () => {
let authService: any;
let actions$: Observable<any>;
let routerService: any;
let dialog: any;

beforeEach(() => {
TestBed.configureTestingModule({
Expand All @@ -34,13 +38,20 @@ describe('AuthEffects', () => {
provide: Router,
useValue: { navigate: jest.fn() },
},
{
provide: MatDialog,
useValue: {
open: jest.fn(),
},
},
],
});

effects = TestBed.get(AuthEffects);
authService = TestBed.get(AuthService);
actions$ = TestBed.get(Actions);
routerService = TestBed.get(Router);
dialog = TestBed.get(MatDialog);

spyOn(routerService, 'navigate').and.callThrough();
});
Expand Down Expand Up @@ -109,4 +120,34 @@ describe('AuthEffects', () => {
});
});
});

describe('logoutConfirmation$', () => {
it('should dispatch a Logout action if dialog closes with true result', () => {
const action = new LogoutConfirmation();
const completion = new Logout();

actions$ = hot('-a', { a: action });
const expected = cold('-b', { b: completion });

dialog.open = () => ({
afterClosed: jest.fn(() => of(true)),
});

expect(effects.logoutConfirmation$).toBeObservable(expected);
});

it('should dispatch a LogoutConfirmationDismiss action if dialog closes with falsy result', () => {
const action = new LogoutConfirmation();
const completion = new LogoutConfirmationDismiss();

actions$ = hot('-a', { a: action });
const expected = cold('-b', { b: completion });

dialog.open = () => ({
afterClosed: jest.fn(() => of(false)),
});

expect(effects.logoutConfirmation$).toBeObservable(expected);
});
});
});
22 changes: 21 additions & 1 deletion projects/example-app/src/app/auth/effects/auth.effects.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Injectable } from '@angular/core';
import { MatDialog } from '@angular/material';
import { Router } from '@angular/router';
import { Actions, Effect, ofType } from '@ngrx/effects';
import { of } from 'rxjs';
Expand All @@ -9,9 +10,12 @@ import {
Login,
LoginFailure,
LoginSuccess,
Logout,
LogoutConfirmationDismiss,
} from '../actions/auth.actions';
import { Authenticate } from '../models/user';
import { AuthService } from '../services/auth.service';
import { LogoutConfirmationDialogComponent } from '../components/logout-confirmation-dialog.component';

@Injectable()
export class AuthEffects {
Expand Down Expand Up @@ -41,9 +45,25 @@ export class AuthEffects {
})
);

@Effect()
logoutConfirmation$ = this.actions$.pipe(
ofType(AuthActionTypes.LogoutConfirmation),
exhaustMap(() => {
const dialogRef = this.dialog.open<
LogoutConfirmationDialogComponent,
undefined,
boolean
>(LogoutConfirmationDialogComponent);

return dialogRef.afterClosed();
}),
map(result => (result ? new Logout() : new LogoutConfirmationDismiss()))
);

constructor(
private actions$: Actions,
private authService: AuthService,
private router: Router
private router: Router,
private dialog: MatDialog
) {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,6 @@ export class AppComponent {
logout() {
this.closeSidenav();

this.store.dispatch(new AuthActions.Logout());
this.store.dispatch(new AuthActions.LogoutConfirmation());
}
}
3 changes: 3 additions & 0 deletions projects/example-app/src/app/material/material.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
MatIconModule,
MatToolbarModule,
MatProgressSpinnerModule,
MatDialogModule,
} from '@angular/material';

@NgModule({
Expand All @@ -21,6 +22,7 @@ import {
MatIconModule,
MatToolbarModule,
MatProgressSpinnerModule,
MatDialogModule,
],
exports: [
MatInputModule,
Expand All @@ -31,6 +33,7 @@ import {
MatIconModule,
MatToolbarModule,
MatProgressSpinnerModule,
MatDialogModule,
],
})
export class MaterialModule {}

0 comments on commit ba8d300

Please sign in to comment.