+ class="sky-toast-content">
diff --git a/src/modules/toast/toast-messages/toast.component.scss b/src/modules/toast/toast-messages/toast.component.scss
index 7260d20fa..fdcea431a 100644
--- a/src/modules/toast/toast-messages/toast.component.scss
+++ b/src/modules/toast/toast-messages/toast.component.scss
@@ -11,7 +11,6 @@
flex-direction: row;
align-items: center;
opacity: 1;
- cursor: pointer;
&:hover {
@include sky-shadow;
@@ -43,15 +42,6 @@
}
}
-.sky-toast-closing {
- opacity: 0;
- -webkit-transition: opacity linear 0.5s;
- -moz-transition: opacity linear 0.5s;
- -ms-transition: opacity linear 0.5s;
- -o-transition: opacity linear 0.5s;
- transition: opacity linear 0.5s;
-}
-
.sky-toast-info {
background-color: $sky-background-color-info;
border-color: $sky-highlight-color-info;
diff --git a/src/modules/toast/toast-messages/toast.component.spec.ts b/src/modules/toast/toast-messages/toast.component.spec.ts
index 46010b26c..5ffdec7ba 100644
--- a/src/modules/toast/toast-messages/toast.component.spec.ts
+++ b/src/modules/toast/toast-messages/toast.component.spec.ts
@@ -1,17 +1,20 @@
-import { TestBed } from '@angular/core/testing';
import {
- SkyToastMessage,
- SkyToastCustomComponent
+ TestBed
+} from '@angular/core/testing';
+import {
+ SkyToastInstance
} from '../types';
-import { SkyToastComponent } from '.';
+import {
+ SkyToastComponent
+} from '.';
import {
ComponentFactoryResolver,
Injector
} from '@angular/core';
describe('Toast service', () => {
- class TestComponent implements SkyToastCustomComponent { public message: SkyToastMessage; }
- let message: SkyToastMessage;
+ class TestComponent { constructor(public message: SkyToastInstance) {} }
+ let message: SkyToastInstance;
beforeEach(() => {
TestBed.configureTestingModule({
@@ -64,7 +67,7 @@ describe('Toast service', () => {
it('should instantiate a toast without a custom component',
() => {
- message = new SkyToastMessage('My message', undefined, 'danger', () => {}, []);
+ message = new SkyToastInstance('My message', undefined, 'danger', [], () => {});
let toast: SkyToastComponent;
try {
toast = new SkyToastComponent(undefined, undefined);
@@ -81,13 +84,27 @@ describe('Toast service', () => {
expect(toast).toBeTruthy();
});
+ it('should show proper closed or open states', () => {
+ message = new SkyToastInstance('My message', undefined, 'danger', [], () => {});
+ let toast: SkyToastComponent = new SkyToastComponent(undefined, undefined);
+ toast.message = message;
+
+ toast.ngOnInit();
+
+ expect(toast.getAnimationState()).toBe('toastOpen');
+ message.close();
+ toast.animationDone(undefined);
+ expect(toast.getAnimationState()).toBe('toastClosed');
+ expect(toast).toBeTruthy();
+ });
+
it('should instantiate a toast with a custom component and tear it down',
() => {
let clearCalled: boolean = false;
let createComponentCalled: boolean = false;
let destroyCalled: boolean = false;
- message = new SkyToastMessage(undefined, TestComponent, 'danger', () => {}, []);
+ message = new SkyToastInstance(undefined, TestComponent, 'danger', [], () => {});
let toast: SkyToastComponent;
toast = new SkyToastComponent(TestBed.get(ComponentFactoryResolver), TestBed.get(Injector));
diff --git a/src/modules/toast/toast-messages/toast.component.ts b/src/modules/toast/toast-messages/toast.component.ts
index e068e716e..b0cf879e0 100644
--- a/src/modules/toast/toast-messages/toast.component.ts
+++ b/src/modules/toast/toast-messages/toast.component.ts
@@ -8,32 +8,52 @@ import {
ViewContainerRef,
ReflectiveInjector,
ComponentRef,
- Injector
+ Injector,
+ trigger,
+ state,
+ style,
+ animate,
+ transition,
+ ChangeDetectionStrategy
} from '@angular/core';
import {
- SkyToastMessage,
- SkyToastCustomComponent
+ SkyToastInstance
} from '../types';
+const TOAST_OPEN_STATE = 'toastOpen';
+const TOAST_CLOSED_STATE = 'toastClosed';
+
@Component({
selector: 'sky-toast',
templateUrl: './toast.component.html',
- styleUrls: ['./toast.component.scss']
+ styleUrls: ['./toast.component.scss'],
+ animations: [
+ trigger('toastState', [
+ state(TOAST_OPEN_STATE, style({ opacity: 1 })),
+ state(TOAST_CLOSED_STATE, style({ opacity: 0 })),
+ transition(`* <=> *`, animate('500ms linear'))
+ ])
+ ],
+ changeDetection: ChangeDetectionStrategy.OnPush
})
export class SkyToastComponent implements OnInit, OnDestroy {
@Input('message')
- public message: SkyToastMessage;
+ public message: SkyToastInstance;
@ViewChild('skytoastcustomtemplate', { read: ViewContainerRef })
private customToastHost: ViewContainerRef;
- private customComponent: ComponentRef;
+ private customComponent: ComponentRef;
constructor(
private resolver: ComponentFactoryResolver,
private injector: Injector
) {}
+ public getAnimationState(): string {
+ return !this.message.isClosing.isStopped ? TOAST_OPEN_STATE : TOAST_CLOSED_STATE;
+ }
+
public ngOnInit() {
if (this.message.customComponentType) {
this.loadComponent();
@@ -46,8 +66,16 @@ export class SkyToastComponent implements OnInit, OnDestroy {
}
}
+ public animationDone(event: AnimationEvent) {
+ this.message.isClosed.emit();
+ }
+
private loadComponent() {
this.customToastHost.clear();
+ this.message.providers.push({
+ provide: SkyToastInstance,
+ useValue: this.message
+ });
let componentFactory = this.resolver.resolveComponentFactory(this.message.customComponentType);
let providers = ReflectiveInjector.resolve(this.message.providers || []);
diff --git a/src/modules/toast/toast.module.ts b/src/modules/toast/toast.module.ts
index 71fcdcf76..f2ce55b6a 100644
--- a/src/modules/toast/toast.module.ts
+++ b/src/modules/toast/toast.module.ts
@@ -1,16 +1,31 @@
-import { NgModule } from '@angular/core';
-import { CommonModule } from '@angular/common';
-import { SkyToastService } from './services/toast.service';
-import { SkyToastContainerComponent } from './toast-container.component';
-import { SkyResourcesModule } from '../resources';
-import { SkyToastAdapterService } from './services/toast-adapter.service';
-import { SkyToastComponent } from './toast-messages';
+import {
+ NgModule
+} from '@angular/core';
+import {
+ CommonModule
+} from '@angular/common';
+import {
+ SkyToastService
+} from './services/toast.service';
+import {
+ SkyToastContainerComponent
+} from './toast-container.component';
+import {
+ SkyResourcesModule
+} from '../resources';
+import {
+ SkyToastAdapterService
+} from './services/toast-adapter.service';
+import {
+ SkyToastComponent
+} from './toast-messages';
export {
- SkyToastMessage,
- SkyToastCustomComponent
+ SkyToastInstance
} from './types';
-export { SkyToastService };
+export {
+ SkyToastService
+};
@NgModule({
declarations: [
@@ -33,5 +48,4 @@ export { SkyToastService };
SkyToastComponent
]
})
-export class SkyToastModule {
-}
+export class SkyToastModule {}
diff --git a/src/modules/toast/types/index.ts b/src/modules/toast/types/index.ts
index 0f189635e..1bb7d3b47 100644
--- a/src/modules/toast/types/index.ts
+++ b/src/modules/toast/types/index.ts
@@ -1,4 +1,3 @@
export * from './toast-config';
-export * from './toast-custom';
-export * from './toast-message';
-export * from './toast-message-type';
+export * from './toast-instance';
+export * from './toast-type';
diff --git a/src/modules/toast/types/toast-config.ts b/src/modules/toast/types/toast-config.ts
index 95b01362d..964b07a21 100644
--- a/src/modules/toast/types/toast-config.ts
+++ b/src/modules/toast/types/toast-config.ts
@@ -1,4 +1,6 @@
-import { SkyToastType } from './toast-message-type';
+import {
+ SkyToastType
+} from './toast-type';
import {
Type,
Provider
diff --git a/src/modules/toast/types/toast-custom.ts b/src/modules/toast/types/toast-custom.ts
deleted file mode 100644
index c5dd23906..000000000
--- a/src/modules/toast/types/toast-custom.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-import { SkyToastMessage } from './toast-message';
-
-export interface SkyToastCustomComponent {
- message: SkyToastMessage;
-}
diff --git a/src/modules/toast/types/toast-instance.ts b/src/modules/toast/types/toast-instance.ts
new file mode 100644
index 000000000..c6c161a74
--- /dev/null
+++ b/src/modules/toast/types/toast-instance.ts
@@ -0,0 +1,33 @@
+import {
+ Subject
+} from 'rxjs';
+import {
+ Type,
+ Provider,
+ EventEmitter
+} from '@angular/core';
+
+export class SkyToastInstance {
+ public isClosed = new EventEmitter();
+ public isClosing = new Subject();
+
+ constructor(
+ public message: string,
+ public customComponentType: Type,
+ public toastType: string,
+ public providers: Provider[] = [],
+ private removeFromQueue: Function
+ ) {}
+
+ public close = () => {
+ if (!this.isClosing.isStopped) {
+ this.isClosing.next();
+ this.isClosing.complete();
+
+ this.isClosed.subscribe(() => {
+ this.removeFromQueue(this);
+ this.isClosed.complete();
+ });
+ }
+ }
+}
diff --git a/src/modules/toast/types/toast-message.ts b/src/modules/toast/types/toast-message.ts
deleted file mode 100644
index ab139b480..000000000
--- a/src/modules/toast/types/toast-message.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-import { BehaviorSubject } from 'rxjs/BehaviorSubject';
-import { Observable } from 'rxjs/Observable';
-import {
- Type,
- Provider
-} from '@angular/core';
-
-export class SkyToastMessage {
- private _isClosed: BehaviorSubject;
- private _isClosing: BehaviorSubject;
-
- public get isClosed(): Observable { return this._isClosed.asObservable(); }
- public get isClosing(): Observable { return this._isClosing.asObservable(); }
-
- constructor(
- public message: string,
- public customComponentType: Type,
- public toastType: string,
- private removeFromQueue: Function,
- public providers: Provider[] = []
- ) {
- this._isClosed = new BehaviorSubject(false);
- this._isClosing = new BehaviorSubject(false);
- }
-
- public close = () => {
- if (!this._isClosing.getValue()) {
- this._isClosing.next(true);
-
- setTimeout(() => {
- this.removeFromQueue(this);
- this._isClosed.next(true);
- }, 500);
- }
- }
-}
diff --git a/src/modules/toast/types/toast-message-type.ts b/src/modules/toast/types/toast-type.ts
similarity index 100%
rename from src/modules/toast/types/toast-message-type.ts
rename to src/modules/toast/types/toast-type.ts
From 2fa1e41fa7fc552b43e65ad5255bd9386a784e6b Mon Sep 17 00:00:00 2001
From: Conor Wright
Date: Fri, 4 May 2018 11:17:58 -0400
Subject: [PATCH 27/46] prettied up the demo a little
---
.../src/app/toast/toast-demo.component.html | 2 +-
.../src/app/toast/toast-demo.component.ts | 7 +++--
.../src/app/toast/toast-visual.component.ts | 8 +++--
.../src/app/toast/toast.visual-spec.ts | 5 ++-
.../toast/toast-custom-demo.component.html | 8 +++++
.../toast/toast-custom-demo.component.ts | 7 +++--
src/demos/toast/toast-demo.component.html | 31 ++++++++-----------
src/demos/toast/toast-demo.component.ts | 8 +++--
.../services/toast-adapter.service.spec.ts | 8 +++--
.../toast/services/toast-adapter.service.ts | 1 +
.../toast/services/toast.service.spec.ts | 24 +++++++-------
src/modules/toast/services/toast.service.ts | 2 ++
.../toast/toast-container.component.spec.ts | 8 +++--
.../toast/toast-container.component.ts | 14 ++++++---
.../toast/toast-messages/toast.component.html | 5 +--
.../toast-messages/toast.component.spec.ts | 9 +++---
.../toast/toast-messages/toast.component.ts | 1 +
src/modules/toast/toast.module.ts | 8 +++--
src/modules/toast/types/toast-config.ts | 7 +++--
src/modules/toast/types/toast-instance.ts | 7 +++--
20 files changed, 103 insertions(+), 67 deletions(-)
create mode 100644 src/demos/toast/toast-custom-demo.component.html
diff --git a/skyux-spa-visual-tests/src/app/toast/toast-demo.component.html b/skyux-spa-visual-tests/src/app/toast/toast-demo.component.html
index ed8a9b220..cd3a16d25 100644
--- a/skyux-spa-visual-tests/src/app/toast/toast-demo.component.html
+++ b/skyux-spa-visual-tests/src/app/toast/toast-demo.component.html
@@ -1 +1 @@
-Toast component
+Toast component
diff --git a/skyux-spa-visual-tests/src/app/toast/toast-demo.component.ts b/skyux-spa-visual-tests/src/app/toast/toast-demo.component.ts
index 6d317f2e7..7fa4161a9 100644
--- a/skyux-spa-visual-tests/src/app/toast/toast-demo.component.ts
+++ b/skyux-spa-visual-tests/src/app/toast/toast-demo.component.ts
@@ -1,6 +1,8 @@
import {
- Component
+ Component,
+ ChangeDetectionStrategy
} from '@angular/core';
+
import {
SkyToastService,
SkyToastInstance
@@ -9,7 +11,8 @@ import {
@Component({
selector: 'sky-test-cmp-toast',
templateUrl: './toast-demo.component.html',
- providers: [SkyToastService]
+ providers: [SkyToastService],
+ changeDetection: ChangeDetectionStrategy.OnPush
})
export class ToastDemoComponent {
constructor(public message: SkyToastInstance) {}
diff --git a/skyux-spa-visual-tests/src/app/toast/toast-visual.component.ts b/skyux-spa-visual-tests/src/app/toast/toast-visual.component.ts
index 33c96159b..4b11c40ee 100644
--- a/skyux-spa-visual-tests/src/app/toast/toast-visual.component.ts
+++ b/skyux-spa-visual-tests/src/app/toast/toast-visual.component.ts
@@ -1,16 +1,20 @@
import {
- Component
+ Component,
+ ChangeDetectionStrategy
} from '@angular/core';
+
import {
SkyToastService
} from '@blackbaud/skyux/dist/core';
+
import {
ToastDemoComponent
} from './toast-demo.component';
@Component({
selector: 'toast-visual',
- templateUrl: './toast-visual.component.html'
+ templateUrl: './toast-visual.component.html',
+ changeDetection: ChangeDetectionStrategy.OnPush
})
export class ToastVisualComponent {
constructor(
diff --git a/skyux-spa-visual-tests/src/app/toast/toast.visual-spec.ts b/skyux-spa-visual-tests/src/app/toast/toast.visual-spec.ts
index da7a2ae2d..df221c0d2 100644
--- a/skyux-spa-visual-tests/src/app/toast/toast.visual-spec.ts
+++ b/skyux-spa-visual-tests/src/app/toast/toast.visual-spec.ts
@@ -1,4 +1,7 @@
-import { SkyVisualTest } from '../../../config/utils/visual-test-commands';
+import {
+ SkyVisualTest
+} from '../../../config/utils/visual-test-commands';
+
import {
element,
by
diff --git a/src/demos/toast/toast-custom-demo.component.html b/src/demos/toast/toast-custom-demo.component.html
new file mode 100644
index 000000000..9f43bdce6
--- /dev/null
+++ b/src/demos/toast/toast-custom-demo.component.html
@@ -0,0 +1,8 @@
+
+ {{text}}
+
+ example.com
+
+
diff --git a/src/demos/toast/toast-custom-demo.component.ts b/src/demos/toast/toast-custom-demo.component.ts
index 6fa42c45a..b526898e5 100644
--- a/src/demos/toast/toast-custom-demo.component.ts
+++ b/src/demos/toast/toast-custom-demo.component.ts
@@ -1,14 +1,17 @@
import {
Component,
- OnInit
+ OnInit,
+ ChangeDetectionStrategy
} from '@angular/core';
+
import {
SkyToastInstance
} from '../../core';
@Component({
selector: 'sky-toast-custom-demo',
- template: "{{text}}example.com
"
+ templateUrl: './toast-custom-demo.component.html',
+ changeDetection: ChangeDetectionStrategy.OnPush
})
export class SkyToastCustomDemoComponent implements OnInit {
public text = 'This is a templated message. It can even link you to ';
diff --git a/src/demos/toast/toast-demo.component.html b/src/demos/toast/toast-demo.component.html
index c3b8d6127..48793cfe8 100644
--- a/src/demos/toast/toast-demo.component.html
+++ b/src/demos/toast/toast-demo.component.html
@@ -1,22 +1,17 @@
-
+
+ Toast type
+
+
+
+ {{ typeTranslator[type] }}
+
+
+
+
diff --git a/src/demos/toast/toast-demo.component.ts b/src/demos/toast/toast-demo.component.ts
index d0c30025d..d1d21d702 100644
--- a/src/demos/toast/toast-demo.component.ts
+++ b/src/demos/toast/toast-demo.component.ts
@@ -1,17 +1,21 @@
import {
- Component
+ Component,
+ ChangeDetectionStrategy
} from '@angular/core';
+
import {
SkyToastService,
SkyToastType
} from '../../core';
+
import {
SkyToastCustomDemoComponent
} from './toast-custom-demo.component';
@Component({
selector: 'sky-toast-demo',
- templateUrl: './toast-demo.component.html'
+ templateUrl: './toast-demo.component.html',
+ changeDetection: ChangeDetectionStrategy.OnPush
})
export class SkyToastDemoComponent {
public selectedType = SkyToastType.Info;
diff --git a/src/modules/toast/services/toast-adapter.service.spec.ts b/src/modules/toast/services/toast-adapter.service.spec.ts
index 57fcf993c..cc20ca31b 100644
--- a/src/modules/toast/services/toast-adapter.service.spec.ts
+++ b/src/modules/toast/services/toast-adapter.service.spec.ts
@@ -1,12 +1,14 @@
+import {
+ RendererFactory2
+} from '@angular/core';
import {
TestBed
} from '@angular/core/testing';
+
import {
SkyWindowRefService
} from '../../window';
-import {
- RendererFactory2
-} from '@angular/core';
+
import {
SkyToastAdapterService
} from './toast-adapter.service';
diff --git a/src/modules/toast/services/toast-adapter.service.ts b/src/modules/toast/services/toast-adapter.service.ts
index 4d9b59f95..b3f2e08b5 100644
--- a/src/modules/toast/services/toast-adapter.service.ts
+++ b/src/modules/toast/services/toast-adapter.service.ts
@@ -3,6 +3,7 @@ import {
Renderer2,
RendererFactory2
} from '@angular/core';
+
import {
SkyWindowRefService
} from '../../window';
diff --git a/src/modules/toast/services/toast.service.spec.ts b/src/modules/toast/services/toast.service.spec.ts
index bde0da96a..7aa44bd10 100644
--- a/src/modules/toast/services/toast.service.spec.ts
+++ b/src/modules/toast/services/toast.service.spec.ts
@@ -1,25 +1,27 @@
+import {
+ ApplicationRef,
+ ComponentFactoryResolver,
+ Injector
+} from '@angular/core';
import {
TestBed
} from '@angular/core/testing';
+
+import {
+ SkyWindowRefService
+} from '../../window';
+
import {
SkyToastService
} from './toast.service';
+import {
+ SkyToastAdapterService
+} from './toast-adapter.service';
import {
SkyToastConfig,
SkyToastType,
SkyToastInstance
} from '../types';
-import {
- SkyWindowRefService
-} from '../../window';
-import {
- SkyToastAdapterService
-} from './toast-adapter.service';
-import {
- ApplicationRef,
- ComponentFactoryResolver,
- Injector
-} from '@angular/core';
describe('Toast service', () => {
class TestComponent { constructor(public message: SkyToastInstance) { } }
diff --git a/src/modules/toast/services/toast.service.ts b/src/modules/toast/services/toast.service.ts
index f7b91291f..410427780 100644
--- a/src/modules/toast/services/toast.service.ts
+++ b/src/modules/toast/services/toast.service.ts
@@ -9,9 +9,11 @@ import {
Type,
Provider
} from '@angular/core';
+
import {
BehaviorSubject
} from 'rxjs';
+
import {
SkyToastContainerComponent
} from '../toast-container.component';
diff --git a/src/modules/toast/toast-container.component.spec.ts b/src/modules/toast/toast-container.component.spec.ts
index 52875b47a..7ab577077 100644
--- a/src/modules/toast/toast-container.component.spec.ts
+++ b/src/modules/toast/toast-container.component.spec.ts
@@ -1,6 +1,11 @@
import {
TestBed
} from '@angular/core/testing';
+
+import {
+ BehaviorSubject
+} from 'rxjs';
+
import {
SkyToastService,
SkyToastContainerComponent
@@ -8,9 +13,6 @@ import {
import {
SkyToastInstance
} from './types';
-import {
- BehaviorSubject
-} from 'rxjs/BehaviorSubject';
describe('Toast service', () => {
let toastService: SkyToastService;
diff --git a/src/modules/toast/toast-container.component.ts b/src/modules/toast/toast-container.component.ts
index da159aac4..c512eae76 100644
--- a/src/modules/toast/toast-container.component.ts
+++ b/src/modules/toast/toast-container.component.ts
@@ -1,18 +1,22 @@
import {
Component,
- OnInit
+ OnInit,
+ ChangeDetectionStrategy
} from '@angular/core';
+
+import {
+ Observable
+} from 'rxjs';
+
import {
SkyToastService
} from './services/toast.service';
-import {
- Observable
-} from 'rxjs/Observable';
@Component({
selector: 'sky-toaster',
templateUrl: './toast-container.component.html',
- styleUrls: ['./toast-container.component.scss']
+ styleUrls: ['./toast-container.component.scss'],
+ changeDetection: ChangeDetectionStrategy.OnPush
})
export class SkyToastContainerComponent implements OnInit {
diff --git a/src/modules/toast/toast-messages/toast.component.html b/src/modules/toast/toast-messages/toast.component.html
index ef4710b19..166c4f706 100644
--- a/src/modules/toast/toast-messages/toast.component.html
+++ b/src/modules/toast/toast-messages/toast.component.html
@@ -3,10 +3,7 @@
[@toastState]="getAnimationState()">
diff --git a/src/modules/toast/toast-messages/toast.component.spec.ts b/src/modules/toast/toast-messages/toast.component.spec.ts
index 5ffdec7ba..590b67c87 100644
--- a/src/modules/toast/toast-messages/toast.component.spec.ts
+++ b/src/modules/toast/toast-messages/toast.component.spec.ts
@@ -1,16 +1,17 @@
+import {
+ ComponentFactoryResolver,
+ Injector
+} from '@angular/core';
import {
TestBed
} from '@angular/core/testing';
+
import {
SkyToastInstance
} from '../types';
import {
SkyToastComponent
} from '.';
-import {
- ComponentFactoryResolver,
- Injector
-} from '@angular/core';
describe('Toast service', () => {
class TestComponent { constructor(public message: SkyToastInstance) {} }
diff --git a/src/modules/toast/toast-messages/toast.component.ts b/src/modules/toast/toast-messages/toast.component.ts
index b0cf879e0..6ebb138b1 100644
--- a/src/modules/toast/toast-messages/toast.component.ts
+++ b/src/modules/toast/toast-messages/toast.component.ts
@@ -16,6 +16,7 @@ import {
transition,
ChangeDetectionStrategy
} from '@angular/core';
+
import {
SkyToastInstance
} from '../types';
diff --git a/src/modules/toast/toast.module.ts b/src/modules/toast/toast.module.ts
index f2ce55b6a..5911b12bc 100644
--- a/src/modules/toast/toast.module.ts
+++ b/src/modules/toast/toast.module.ts
@@ -4,15 +4,17 @@ import {
import {
CommonModule
} from '@angular/common';
+
+import {
+ SkyResourcesModule
+} from '../resources';
+
import {
SkyToastService
} from './services/toast.service';
import {
SkyToastContainerComponent
} from './toast-container.component';
-import {
- SkyResourcesModule
-} from '../resources';
import {
SkyToastAdapterService
} from './services/toast-adapter.service';
diff --git a/src/modules/toast/types/toast-config.ts b/src/modules/toast/types/toast-config.ts
index 964b07a21..96bf785f6 100644
--- a/src/modules/toast/types/toast-config.ts
+++ b/src/modules/toast/types/toast-config.ts
@@ -1,11 +1,12 @@
-import {
- SkyToastType
-} from './toast-type';
import {
Type,
Provider
} from '@angular/core';
+import {
+ SkyToastType
+} from './toast-type';
+
export interface SkyToastConfig {
message?: string;
customComponentType?: Type
;
diff --git a/src/modules/toast/types/toast-instance.ts b/src/modules/toast/types/toast-instance.ts
index c6c161a74..11013297d 100644
--- a/src/modules/toast/types/toast-instance.ts
+++ b/src/modules/toast/types/toast-instance.ts
@@ -1,12 +1,13 @@
-import {
- Subject
-} from 'rxjs';
import {
Type,
Provider,
EventEmitter
} from '@angular/core';
+import {
+ Subject
+} from 'rxjs';
+
export class SkyToastInstance {
public isClosed = new EventEmitter();
public isClosing = new Subject();
From 57bfe9c7e7c1cb12f9bf47fd0249fee0b7cafe62 Mon Sep 17 00:00:00 2001
From: Conor Wright
Date: Fri, 4 May 2018 14:17:06 -0400
Subject: [PATCH 28/46] updated for the remaining PR comments
---
.../src/app/toast/toast-demo.component.ts | 4 +++-
src/demos/demo.service.ts | 10 ++++++++++
src/demos/toast/toast-custom-demo.component.ts | 4 +++-
src/demos/toast/toast-demo.component.ts | 10 +++++-----
src/locales/resources_en_US.json | 2 +-
src/modules/toast/services/toast.service.ts | 6 +++---
src/modules/toast/toast-container.component.html | 1 -
src/modules/toast/toast-container.component.ts | 4 +++-
.../toast/toast-messages/toast.component.html | 3 ++-
.../toast/toast-messages/toast.component.scss | 12 ++++++------
src/modules/toast/toast-messages/toast.component.ts | 6 +++---
11 files changed, 39 insertions(+), 23 deletions(-)
diff --git a/skyux-spa-visual-tests/src/app/toast/toast-demo.component.ts b/skyux-spa-visual-tests/src/app/toast/toast-demo.component.ts
index 7fa4161a9..1bc1611f9 100644
--- a/skyux-spa-visual-tests/src/app/toast/toast-demo.component.ts
+++ b/skyux-spa-visual-tests/src/app/toast/toast-demo.component.ts
@@ -15,5 +15,7 @@ import {
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ToastDemoComponent {
- constructor(public message: SkyToastInstance) {}
+ constructor(
+ public message: SkyToastInstance
+ ) {}
}
diff --git a/src/demos/demo.service.ts b/src/demos/demo.service.ts
index 5231cb6cf..f7e9f6bb5 100644
--- a/src/demos/demo.service.ts
+++ b/src/demos/demo.service.ts
@@ -1031,6 +1031,16 @@ export class SkyDemoService {
fileContents: require('!!raw-loader!./toast/toast-demo.component.ts'),
componentName: 'SkyToastDemoComponent',
bootstrapSelector: 'sky-toast-demo'
+ },
+ {
+ name: 'toast-custom-demo.component.html',
+ fileContents: require('!!raw-loader!./toast/toast-custom-demo.component.html')
+ },
+ {
+ name: 'toast-custom-demo.component.ts',
+ fileContents: require('!!raw-loader!./toast/toast-custom-demo.component.ts'),
+ componentName: 'SkyToastCustomDemoComponent',
+ bootstrapSelector: 'sky-toast-custom-demo'
}
]
},
diff --git a/src/demos/toast/toast-custom-demo.component.ts b/src/demos/toast/toast-custom-demo.component.ts
index b526898e5..6f0684ebb 100644
--- a/src/demos/toast/toast-custom-demo.component.ts
+++ b/src/demos/toast/toast-custom-demo.component.ts
@@ -16,7 +16,9 @@ import {
export class SkyToastCustomDemoComponent implements OnInit {
public text = 'This is a templated message. It can even link you to ';
- constructor(public message: SkyToastInstance) {}
+ constructor(
+ public message: SkyToastInstance
+ ) {}
public ngOnInit() {
this.message.isClosing.subscribe(() => {
diff --git a/src/demos/toast/toast-demo.component.ts b/src/demos/toast/toast-demo.component.ts
index d1d21d702..e43dea5d7 100644
--- a/src/demos/toast/toast-demo.component.ts
+++ b/src/demos/toast/toast-demo.component.ts
@@ -1,6 +1,5 @@
import {
- Component,
- ChangeDetectionStrategy
+ Component
} from '@angular/core';
import {
@@ -14,15 +13,16 @@ import {
@Component({
selector: 'sky-toast-demo',
- templateUrl: './toast-demo.component.html',
- changeDetection: ChangeDetectionStrategy.OnPush
+ templateUrl: './toast-demo.component.html'
})
export class SkyToastDemoComponent {
public selectedType = SkyToastType.Info;
public typeTranslator = SkyToastType;
public types = [SkyToastType.Info, SkyToastType.Success, SkyToastType.Warning, SkyToastType.Danger];
- constructor(private toastSvc: SkyToastService) { }
+ constructor(
+ private toastSvc: SkyToastService
+ ) {}
public openMessage() {
this.toastSvc.openMessage('This is a ' + SkyToastType[this.selectedType] + ' toast.', {toastType: this.selectedType});
diff --git a/src/locales/resources_en_US.json b/src/locales/resources_en_US.json
index 845592ac1..e3422acdc 100644
--- a/src/locales/resources_en_US.json
+++ b/src/locales/resources_en_US.json
@@ -835,7 +835,7 @@
"_description": "The close button for the timepicker modal",
"message": "Done"
},
- "toast_close": {
+ "toast_close_button": {
"_description": "Screen reader text for the close button on toasts",
"message": "Close the toast"
},
diff --git a/src/modules/toast/services/toast.service.ts b/src/modules/toast/services/toast.service.ts
index 410427780..b9c20daf4 100644
--- a/src/modules/toast/services/toast.service.ts
+++ b/src/modules/toast/services/toast.service.ts
@@ -40,20 +40,20 @@ export class SkyToastService implements OnDestroy {
private adapter: SkyToastAdapterService
) {}
- public openMessage(message: string, config: SkyToastConfig = {}) {
+ public openMessage(message: string, config: SkyToastConfig = {}): SkyToastInstance {
config.message = message;
config.customComponentType = undefined;
return this.open(config);
}
- public openTemplatedMessage(customComponentType: Type, config: SkyToastConfig = {}, providers?: Provider[]) {
+ public openTemplatedMessage(customComponentType: Type, config: SkyToastConfig = {}, providers?: Provider[]): SkyToastInstance {
config.customComponentType = customComponentType;
config.providers = providers || config.providers;
config.message = undefined;
return this.open(config);
}
- public open(config: SkyToastConfig) {
+ public open(config: SkyToastConfig): SkyToastInstance {
if (!this.host) {
this.host = this.createHostComponent();
}
diff --git a/src/modules/toast/toast-container.component.html b/src/modules/toast/toast-container.component.html
index 71914d146..7a54e40f5 100644
--- a/src/modules/toast/toast-container.component.html
+++ b/src/modules/toast/toast-container.component.html
@@ -1,5 +1,4 @@
;
- constructor(private toast: SkyToastService) { }
+ constructor(
+ private toast: SkyToastService
+ ) {}
public ngOnInit() {
this.messages = this.toast.messages;
diff --git a/src/modules/toast/toast-messages/toast.component.html b/src/modules/toast/toast-messages/toast.component.html
index 166c4f706..bf4b76ad0 100644
--- a/src/modules/toast/toast-messages/toast.component.html
+++ b/src/modules/toast/toast-messages/toast.component.html
@@ -4,7 +4,8 @@
+ [attr.aria-live]="message.toastType==='danger' ? 'assertive' : 'polite'"
+ [attr.role]="message.toastType==='danger' ? 'alert' : undefined">
Date: Tue, 8 May 2018 15:00:56 -0400
Subject: [PATCH 29/46] refactored for PR comments
---
.../toast/toast-custom-demo.component.html | 2 +-
.../toast/toast-custom-demo.component.ts | 11 +-
src/demos/toast/toast-demo.component.html | 2 +-
src/demos/toast/toast-demo.component.ts | 10 +-
src/modules/toast/index.ts | 4 +-
.../toast/services/toast.service.spec.ts | 183 +++++-------------
src/modules/toast/services/toast.service.ts | 98 ++++------
.../toast/toast-container.component.html | 7 -
.../toast/toast-container.component.spec.ts | 48 -----
.../toast/toast-messages/toast.component.html | 12 +-
.../toast-messages/toast.component.spec.ts | 22 +--
.../toast/toast-messages/toast.component.ts | 30 +--
src/modules/toast/toast.module.ts | 10 +-
src/modules/toast/toaster.component.html | 7 +
....component.scss => toaster.component.scss} | 0
src/modules/toast/toaster.component.spec.ts | 49 +++++
...iner.component.ts => toaster.component.ts} | 10 +-
src/modules/toast/types/index.ts | 1 -
src/modules/toast/types/toast-config.ts | 10 +-
src/modules/toast/types/toast-instance.ts | 21 +-
src/modules/toast/types/toast-type.ts | 6 -
21 files changed, 207 insertions(+), 336 deletions(-)
delete mode 100644 src/modules/toast/toast-container.component.html
delete mode 100644 src/modules/toast/toast-container.component.spec.ts
create mode 100644 src/modules/toast/toaster.component.html
rename src/modules/toast/{toast-container.component.scss => toaster.component.scss} (100%)
create mode 100644 src/modules/toast/toaster.component.spec.ts
rename src/modules/toast/{toast-container.component.ts => toaster.component.ts} (60%)
delete mode 100644 src/modules/toast/types/toast-type.ts
diff --git a/src/demos/toast/toast-custom-demo.component.html b/src/demos/toast/toast-custom-demo.component.html
index 9f43bdce6..510dd3620 100644
--- a/src/demos/toast/toast-custom-demo.component.html
+++ b/src/demos/toast/toast-custom-demo.component.html
@@ -1,7 +1,7 @@
{{text}}
example.com
diff --git a/src/demos/toast/toast-custom-demo.component.ts b/src/demos/toast/toast-custom-demo.component.ts
index 6f0684ebb..ab719c04b 100644
--- a/src/demos/toast/toast-custom-demo.component.ts
+++ b/src/demos/toast/toast-custom-demo.component.ts
@@ -1,6 +1,5 @@
import {
Component,
- OnInit,
ChangeDetectionStrategy
} from '@angular/core';
@@ -13,16 +12,10 @@ import {
templateUrl: './toast-custom-demo.component.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
-export class SkyToastCustomDemoComponent implements OnInit {
+export class SkyToastCustomDemoComponent {
public text = 'This is a templated message. It can even link you to ';
constructor(
- public message: SkyToastInstance
+ public instance: SkyToastInstance
) {}
-
- public ngOnInit() {
- this.message.isClosing.subscribe(() => {
- this.text = 'Bye bye :D';
- });
- }
}
diff --git a/src/demos/toast/toast-demo.component.html b/src/demos/toast/toast-demo.component.html
index 48793cfe8..fdb6b7c62 100644
--- a/src/demos/toast/toast-demo.component.html
+++ b/src/demos/toast/toast-demo.component.html
@@ -7,7 +7,7 @@
name="radiogroup"
[value]="type"
[(ngModel)]="selectedType">
- {{ typeTranslator[type] }}
+ {{ type }}
diff --git a/src/demos/toast/toast-demo.component.ts b/src/demos/toast/toast-demo.component.ts
index e43dea5d7..d94e0f285 100644
--- a/src/demos/toast/toast-demo.component.ts
+++ b/src/demos/toast/toast-demo.component.ts
@@ -3,8 +3,7 @@ import {
} from '@angular/core';
import {
- SkyToastService,
- SkyToastType
+ SkyToastService
} from '../../core';
import {
@@ -16,16 +15,15 @@ import {
templateUrl: './toast-demo.component.html'
})
export class SkyToastDemoComponent {
- public selectedType = SkyToastType.Info;
- public typeTranslator = SkyToastType;
- public types = [SkyToastType.Info, SkyToastType.Success, SkyToastType.Warning, SkyToastType.Danger];
+ public selectedType: 'info' | 'success' | 'warning' | 'danger' = 'info';
+ public types = ['info', 'success', 'warning', 'danger'];
constructor(
private toastSvc: SkyToastService
) {}
public openMessage() {
- this.toastSvc.openMessage('This is a ' + SkyToastType[this.selectedType] + ' toast.', {toastType: this.selectedType});
+ this.toastSvc.openMessage('This is a ' + this.selectedType + ' toast.', {toastType: this.selectedType});
}
public openTemplatedMessage() {
diff --git a/src/modules/toast/index.ts b/src/modules/toast/index.ts
index 7de8ff8bf..c5d09997b 100644
--- a/src/modules/toast/index.ts
+++ b/src/modules/toast/index.ts
@@ -1,6 +1,6 @@
export {
- SkyToastContainerComponent
-} from './toast-container.component';
+ SkyToasterComponent
+} from './toaster.component';
export {
SkyToastService
} from './services/toast.service';
diff --git a/src/modules/toast/services/toast.service.spec.ts b/src/modules/toast/services/toast.service.spec.ts
index 7aa44bd10..a3614428d 100644
--- a/src/modules/toast/services/toast.service.spec.ts
+++ b/src/modules/toast/services/toast.service.spec.ts
@@ -18,8 +18,6 @@ import {
SkyToastAdapterService
} from './toast-adapter.service';
import {
- SkyToastConfig,
- SkyToastType,
SkyToastInstance
} from '../types';
@@ -95,18 +93,18 @@ describe('Toast service', () => {
it('should only create a single host component', () => {
const spy = spyOn(toastService as any, 'createHostComponent').and.callThrough();
- toastService.open({message: 'message'});
- toastService.open({message: 'message'});
+ toastService.openMessage('message');
+ toastService.openMessage('message');
expect(spy.calls.count()).toEqual(1);
});
it('should return an instance with a close method', () => {
- const toast = toastService.open({message: 'message'});
+ const toast = toastService.openMessage('message');
expect(typeof toast.close).toEqual('function');
});
it('should expose a method to remove the toast from the DOM', () => {
- let message: SkyToastInstance = toastService.open({message: 'message'});
+ let message: SkyToastInstance = toastService.openMessage('message');
const spy = spyOn(message, 'close').and.callThrough();
toastService.ngOnDestroy();
expect(spy).toHaveBeenCalledWith();
@@ -114,11 +112,7 @@ describe('Toast service', () => {
describe('openMessage() method', () => {
it('should open a toast with the given message and configuration', function() {
- let configuration: SkyToastConfig = {
- toastType: SkyToastType.Danger,
- message: 'fake message'
- };
- let internalMessage: SkyToastInstance = toastService.openMessage('Real message', configuration);
+ let internalMessage: SkyToastInstance = toastService.openMessage('Real message', {toastType: 'danger'});
expect(internalMessage).toBeTruthy();
expect(internalMessage.message).toBe('Real message');
@@ -126,125 +120,35 @@ describe('Toast service', () => {
expect(internalMessage.close).toBeTruthy();
- let isClosingCalled = false;
let isClosedCalled = false;
- internalMessage.isClosing.subscribe(() => isClosingCalled = true);
internalMessage.isClosed.subscribe(() => isClosedCalled = true);
- expect(isClosingCalled).toBeFalsy();
- expect(isClosedCalled).toBeFalsy();
- });
- });
-
- describe('openCustom() method', () => {
- it('should open a custom toast with the given component type and configuration', function() {
- let configuration: SkyToastConfig = {
- message: 'fake message',
- toastType: SkyToastType.Danger
- };
-
- let internalMessage: SkyToastInstance = toastService.openTemplatedMessage(TestComponent, configuration);
-
- expect(internalMessage).toBeTruthy();
- expect(internalMessage.message).toBeFalsy();
- expect(internalMessage.customComponentType).toBeTruthy();
- expect(internalMessage.toastType).toBe('danger');
-
- expect(internalMessage.close).toBeTruthy();
-
- let isClosingCalled = false;
- let isClosedCalled = false;
- internalMessage.isClosing.subscribe(() => isClosingCalled = true);
- internalMessage.isClosed.subscribe(() => isClosedCalled = true);
-
- expect(isClosingCalled).toBeFalsy();
- expect(isClosedCalled).toBeFalsy();
- });
- });
-
- describe('open() method', () => {
- it('should open a toast with the given message and configuration', function() {
- let configuration: SkyToastConfig = {
- toastType: SkyToastType.Danger,
- message: 'My message'
- };
-
- let internalMessage: SkyToastInstance = toastService.open(configuration);
-
- expect(internalMessage).toBeTruthy();
- expect(internalMessage.message).toBe('My message');
- expect(internalMessage.customComponentType).toBeFalsy();
- expect(internalMessage.toastType).toBe('danger');
-
- expect(internalMessage.close).toBeTruthy();
-
- let isClosingCalled = false;
- let isClosedCalled = false;
- internalMessage.isClosing.subscribe(() => isClosingCalled = true);
- internalMessage.isClosed.subscribe(() => isClosedCalled = true);
-
- expect(isClosingCalled).toBeFalsy();
- expect(isClosedCalled).toBeFalsy();
- });
-
- it('should open a custom toast with the given component type and configuration', function() {
- let configuration: SkyToastConfig = {
- toastType: SkyToastType.Danger,
- customComponentType: TestComponent
- };
-
- let internalMessage: SkyToastInstance = toastService.open(configuration);
-
- expect(internalMessage).toBeTruthy();
- expect(internalMessage.message).toBeFalsy();
- expect(internalMessage.customComponentType).toBeTruthy();
- expect(internalMessage.toastType).toBe('danger');
-
- expect(internalMessage.close).toBeTruthy();
-
- let isClosingCalled = false;
- let isClosedCalled = false;
- internalMessage.isClosing.subscribe(() => isClosingCalled = true);
- internalMessage.isClosed.subscribe(() => isClosedCalled = true);
-
- expect(isClosingCalled).toBeFalsy();
+ expect(internalMessage.isOpen).toBeTruthy();
expect(isClosedCalled).toBeFalsy();
});
it('should remove message from queue when the message is closed', function(done: Function) {
- let configuration: SkyToastConfig = {
- toastType: SkyToastType.Danger,
- message: 'My message'
- };
-
- let internalMessage: SkyToastInstance = toastService.open(configuration);
+ let internalMessage: SkyToastInstance = toastService.openMessage('My message', {toastType: 'danger'});
- let isClosingCalled = false;
let isClosedCalled = false;
- internalMessage.isClosing.subscribe(() => isClosingCalled = true);
internalMessage.isClosed.subscribe(() => isClosedCalled = true);
internalMessage.close();
internalMessage.isClosed.next();
setTimeout(() => {
- toastService.messages.subscribe((value) => {
- if (isClosingCalled) {
+ toastService.toastInstances.subscribe((value) => {
+ if (!internalMessage.isOpen) {
expect(value.length).toBe(0);
}
});
- expect(isClosingCalled).toBeTruthy();
+ expect(internalMessage.isOpen).toBeFalsy();
expect(isClosedCalled).toBeTruthy();
done();
}, 600);
});
it('should not error when closing an already closed message', function(done: Function) {
- let configuration: SkyToastConfig = {
- toastType: SkyToastType.Danger,
- message: 'My message'
- };
-
- let internalMessage: SkyToastInstance = toastService.open(configuration);
+ let internalMessage: SkyToastInstance = toastService.openMessage('My message', {toastType: 'danger'});
internalMessage.close();
setTimeout(() => {
try {
@@ -256,27 +160,11 @@ describe('Toast service', () => {
}, 600);
});
- it('should require a message or customComponentType parameter', function() {
- try {
- toastService.open({});
- } catch (error) {
- expect(error).toBe('You must provide either a message or a customComponentType.');
- }
- });
-
- it('should reject both a message and customComponentType being supplied', function() {
- try {
- toastService.open({ message: 'My message', customComponentType: TestComponent });
- } catch (error) {
- expect(error).toBe('You must not provide both a message and a customComponentType.');
- }
- });
-
it('should open specific toast types', function () {
- let infoMessage = toastService.open({message: 'info message', toastType: SkyToastType.Info});
- let warningMessage = toastService.open({message: 'warning message', toastType: SkyToastType.Warning});
- let dangerMessage = toastService.open({message: 'danger message', toastType: SkyToastType.Danger});
- let successMessage = toastService.open({message: 'success message', toastType: SkyToastType.Success});
+ let infoMessage = toastService.openMessage('info message', {toastType: 'info'});
+ let warningMessage = toastService.openMessage('warning message', {toastType: 'warning'});
+ let dangerMessage = toastService.openMessage('danger message', {toastType: 'danger'});
+ let successMessage = toastService.openMessage('success message', {toastType: 'success'});
expect(infoMessage.toastType).toBe('info');
expect(warningMessage.toastType).toBe('warning');
@@ -285,11 +173,44 @@ describe('Toast service', () => {
});
it('should open info toast type when no or an unknown type is supplied', function () {
- let emptyType: SkyToastInstance = toastService.open({message: 'info message'});
- let unknownType: SkyToastInstance = toastService.open({message: 'info message', toastType: 5});
-
+ let emptyType: SkyToastInstance = toastService.openMessage('info message');
expect(emptyType.toastType).toBe('info');
- expect(unknownType.toastType).toBe('info');
+ });
+ });
+
+ describe('openTemplatedMessage() method', () => {
+ it('should open a custom toast with the given component type and configuration', function() {
+ let internalMessage: SkyToastInstance = toastService.openTemplatedMessage(TestComponent, {toastType: 'danger'});
+
+ expect(internalMessage).toBeTruthy();
+ expect(internalMessage.message).toBeFalsy();
+ expect(internalMessage.customComponentType).toBeTruthy();
+ expect(internalMessage.toastType).toBe('danger');
+
+ expect(internalMessage.close).toBeTruthy();
+
+ let isClosedCalled = false;
+ internalMessage.isClosed.subscribe(() => isClosedCalled = true);
+
+ expect(internalMessage.isOpen).toBeTruthy();
+ expect(isClosedCalled).toBeFalsy();
+ });
+
+ it('should open a custom toast with the given component type and configuration', function() {
+ let internalMessage: SkyToastInstance = toastService.openTemplatedMessage(TestComponent, {toastType: 'danger'});
+
+ expect(internalMessage).toBeTruthy();
+ expect(internalMessage.message).toBeFalsy();
+ expect(internalMessage.customComponentType).toBeTruthy();
+ expect(internalMessage.toastType).toBe('danger');
+
+ expect(internalMessage.close).toBeTruthy();
+
+ let isClosedCalled = false;
+ internalMessage.isClosed.subscribe(() => isClosedCalled = true);
+
+ expect(internalMessage.isOpen).toBeTruthy();
+ expect(isClosedCalled).toBeFalsy();
});
});
});
diff --git a/src/modules/toast/services/toast.service.ts b/src/modules/toast/services/toast.service.ts
index b9c20daf4..43085af79 100644
--- a/src/modules/toast/services/toast.service.ts
+++ b/src/modules/toast/services/toast.service.ts
@@ -15,23 +15,22 @@ import {
} from 'rxjs';
import {
- SkyToastContainerComponent
-} from '../toast-container.component';
+ SkyToasterComponent
+} from '../toaster.component';
import {
SkyToastAdapterService
} from './toast-adapter.service';
import {
SkyToastInstance,
- SkyToastConfig,
- SkyToastType
+ SkyToastConfig
} from '../types';
@Injectable()
export class SkyToastService implements OnDestroy {
- private host: ComponentRef;
+ private host: ComponentRef;
- private _messages: SkyToastInstance[] = [];
- public messages: BehaviorSubject = new BehaviorSubject([]);
+ private _toastInstances: SkyToastInstance[] = [];
+ public toastInstances: BehaviorSubject = new BehaviorSubject([]);
constructor(
private appRef: ApplicationRef,
@@ -41,74 +40,57 @@ export class SkyToastService implements OnDestroy {
) {}
public openMessage(message: string, config: SkyToastConfig = {}): SkyToastInstance {
- config.message = message;
- config.customComponentType = undefined;
- return this.open(config);
+ return this.open(config, message);
}
public openTemplatedMessage(customComponentType: Type, config: SkyToastConfig = {}, providers?: Provider[]): SkyToastInstance {
- config.customComponentType = customComponentType;
- config.providers = providers || config.providers;
- config.message = undefined;
- return this.open(config);
- }
-
- public open(config: SkyToastConfig): SkyToastInstance {
- if (!this.host) {
- this.host = this.createHostComponent();
- }
-
- let message: SkyToastInstance = this.createMessage(config);
- this._messages.push(message);
- this.messages.next(this._messages);
-
- return message;
+ return this.open(config, undefined, customComponentType, providers);
}
public ngOnDestroy() {
this.host = undefined;
- this._messages.forEach(message => {
- message.close();
+ this._toastInstances.forEach(instance => {
+ instance.close();
});
- this.messages.next([]);
+ this.toastInstances.next([]);
this.adapter.removeHostElement();
}
- private removeFromQueue: Function = (message: SkyToastInstance) => {
- this._messages = this._messages.filter(msg => msg !== message);
- this.messages.next(this._messages);
- }
-
- private createMessage(config: SkyToastConfig): SkyToastInstance {
- if (!config.message && !config.customComponentType) {
- throw 'You must provide either a message or a customComponentType.';
- }
- if (config.message && config.customComponentType) {
- throw 'You must not provide both a message and a customComponentType.';
+ private open(config: SkyToastConfig, message?: string, customComponentType?: Type, providers?: Provider[]): SkyToastInstance {
+ if (!this.host) {
+ this.host = this.createHostComponent();
}
- let toastType: string = 'info';
- switch (config.toastType) {
- case SkyToastType.Success:
- toastType = 'success';
- break;
- case SkyToastType.Warning:
- toastType = 'warning';
- break;
- case SkyToastType.Danger:
- toastType = 'danger';
- break;
- default:
- toastType = 'info';
- break;
- }
+ let instance: SkyToastInstance = this.createToastInstance(config, message, customComponentType, providers);
+ this._toastInstances.push(instance);
+ this.toastInstances.next(this._toastInstances);
+
+ return instance;
+ }
+
+ private removeFromQueue: Function = (instance: SkyToastInstance) => {
+ this._toastInstances = this._toastInstances.filter(inst => inst !== instance);
+ this.toastInstances.next(this._toastInstances);
+ }
- return new SkyToastInstance(config.message, config.customComponentType, toastType, config.providers, this.removeFromQueue);
+ private createToastInstance(
+ config: SkyToastConfig,
+ message?: string,
+ customComponentType?: Type,
+ providers?: Provider[]
+ ): SkyToastInstance {
+ let newToast = new SkyToastInstance(
+ message,
+ customComponentType,
+ config.toastType ? config.toastType : 'info',
+ providers);
+ newToast.isClosed.subscribe(() => { this.removeFromQueue(newToast); });
+ return newToast;
}
- private createHostComponent(): ComponentRef {
+ private createHostComponent(): ComponentRef {
const componentRef = this.resolver
- .resolveComponentFactory(SkyToastContainerComponent)
+ .resolveComponentFactory(SkyToasterComponent)
.create(this.injector);
const domElem = (componentRef.hostView as EmbeddedViewRef).rootNodes[0];
diff --git a/src/modules/toast/toast-container.component.html b/src/modules/toast/toast-container.component.html
deleted file mode 100644
index 7a54e40f5..000000000
--- a/src/modules/toast/toast-container.component.html
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
diff --git a/src/modules/toast/toast-container.component.spec.ts b/src/modules/toast/toast-container.component.spec.ts
deleted file mode 100644
index 7ab577077..000000000
--- a/src/modules/toast/toast-container.component.spec.ts
+++ /dev/null
@@ -1,48 +0,0 @@
-import {
- TestBed
-} from '@angular/core/testing';
-
-import {
- BehaviorSubject
-} from 'rxjs';
-
-import {
- SkyToastService,
- SkyToastContainerComponent
-} from '.';
-import {
- SkyToastInstance
-} from './types';
-
-describe('Toast service', () => {
- let toastService: SkyToastService;
- let messages: BehaviorSubject = new BehaviorSubject([]);
-
- beforeEach(() => {
- TestBed.configureTestingModule({
- providers: [
- {
- provide: SkyToastService,
- useValue: {
- messages: messages
- }
- }
- ]});
- toastService = TestBed.get(SkyToastService);
- });
-
- it('should instantiate a toast container with its own subscription to the message list',
- (done: Function) => {
- let message: SkyToastInstance = new SkyToastInstance('My message', undefined, 'danger', [], () => {
- messages.next([]);
- });
- messages.next([message]);
-
- let container: SkyToastContainerComponent = new SkyToastContainerComponent(toastService);
- container.ngOnInit();
- container.messages.subscribe((value) => {
- expect(value[0]).toBe(message);
- done();
- });
- });
-});
diff --git a/src/modules/toast/toast-messages/toast.component.html b/src/modules/toast/toast-messages/toast.component.html
index bf4b76ad0..27bed1a62 100644
--- a/src/modules/toast/toast-messages/toast.component.html
+++ b/src/modules/toast/toast-messages/toast.component.html
@@ -3,15 +3,15 @@
[@toastState]="getAnimationState()">
+ [ngClass]="'sky-toast-' + instance.toastType"
+ [attr.aria-live]="instance.toastType==='danger' ? 'assertive' : 'polite'"
+ [attr.role]="instance.toastType==='danger' ? 'alert' : undefined">
- {{message.message}}
+ {{instance.message}}
@@ -19,7 +19,7 @@
class="sky-toast-close"
type="button"
[attr.aria-label]="'toast_close' | skyResources"
- (click)="message.close()">
+ (click)="instance.close()">
diff --git a/src/modules/toast/toast-messages/toast.component.spec.ts b/src/modules/toast/toast-messages/toast.component.spec.ts
index 590b67c87..f2b96fea5 100644
--- a/src/modules/toast/toast-messages/toast.component.spec.ts
+++ b/src/modules/toast/toast-messages/toast.component.spec.ts
@@ -13,9 +13,9 @@ import {
SkyToastComponent
} from '.';
-describe('Toast service', () => {
- class TestComponent { constructor(public message: SkyToastInstance) {} }
- let message: SkyToastInstance;
+describe('Toast component', () => {
+ class TestComponent { constructor(public instance: SkyToastInstance) {} }
+ let instance: SkyToastInstance;
beforeEach(() => {
TestBed.configureTestingModule({
@@ -68,11 +68,11 @@ describe('Toast service', () => {
it('should instantiate a toast without a custom component',
() => {
- message = new SkyToastInstance('My message', undefined, 'danger', [], () => {});
+ instance = new SkyToastInstance('My message', undefined, 'danger', []);
let toast: SkyToastComponent;
try {
toast = new SkyToastComponent(undefined, undefined);
- toast.message = message;
+ toast.instance = instance;
toast.ngOnInit();
expect((toast as any).customComponent).toBeFalsy();
@@ -86,14 +86,14 @@ describe('Toast service', () => {
});
it('should show proper closed or open states', () => {
- message = new SkyToastInstance('My message', undefined, 'danger', [], () => {});
+ instance = new SkyToastInstance('My message', undefined, 'danger', []);
let toast: SkyToastComponent = new SkyToastComponent(undefined, undefined);
- toast.message = message;
+ toast.instance = instance;
toast.ngOnInit();
expect(toast.getAnimationState()).toBe('toastOpen');
- message.close();
+ instance.close();
toast.animationDone(undefined);
expect(toast.getAnimationState()).toBe('toastClosed');
expect(toast).toBeTruthy();
@@ -105,13 +105,13 @@ describe('Toast service', () => {
let createComponentCalled: boolean = false;
let destroyCalled: boolean = false;
- message = new SkyToastInstance(undefined, TestComponent, 'danger', [], () => {});
+ instance = new SkyToastInstance(undefined, TestComponent, 'danger', []);
let toast: SkyToastComponent;
toast = new SkyToastComponent(TestBed.get(ComponentFactoryResolver), TestBed.get(Injector));
- toast.message = message;
+ toast.instance = instance;
- (toast as any).customToastHost = {
+ (toast as any).toastHost = {
clear: () => { clearCalled = true; },
createComponent: () => {
createComponentCalled = true;
diff --git a/src/modules/toast/toast-messages/toast.component.ts b/src/modules/toast/toast-messages/toast.component.ts
index 8263691b7..dddf1a317 100644
--- a/src/modules/toast/toast-messages/toast.component.ts
+++ b/src/modules/toast/toast-messages/toast.component.ts
@@ -32,17 +32,17 @@ const TOAST_CLOSED_STATE = 'toastClosed';
trigger('toastState', [
state(TOAST_OPEN_STATE, style({ opacity: 1 })),
state(TOAST_CLOSED_STATE, style({ opacity: 0 })),
- transition(`* <=> *`, animate('500ms linear'))
+ transition(`toastOpen => toastClosed`, animate('500ms linear'))
])
],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class SkyToastComponent implements OnInit, OnDestroy {
- @Input('message')
- public message: SkyToastInstance;
+ @Input('instance')
+ public instance: SkyToastInstance;
@ViewChild('skytoastcustomtemplate', { read: ViewContainerRef })
- private customToastHost: ViewContainerRef;
+ private toastHost: ViewContainerRef;
private customComponent: ComponentRef
;
@@ -52,11 +52,11 @@ export class SkyToastComponent implements OnInit, OnDestroy {
) {}
public getAnimationState(): string {
- return !this.message.isClosing.isStopped ? TOAST_OPEN_STATE : TOAST_CLOSED_STATE;
+ return this.instance.isOpen ? TOAST_OPEN_STATE : TOAST_CLOSED_STATE;
}
public ngOnInit() {
- if (this.message.customComponentType) {
+ if (this.instance.customComponentType) {
this.loadComponent();
}
}
@@ -68,22 +68,24 @@ export class SkyToastComponent implements OnInit, OnDestroy {
}
public animationDone(event: AnimationEvent) {
- this.message.isClosed.emit();
+ if (!this.instance.isOpen) {
+ this.instance.isClosed.emit();
+ this.instance.isClosed.complete();
+ }
}
private loadComponent() {
- this.customToastHost.clear();
- this.message.providers.push({
+ this.toastHost.clear();
+ this.instance.providers.push({
provide: SkyToastInstance,
- useValue: this.message
+ useValue: this.instance
});
- const componentFactory = this.resolver.resolveComponentFactory(this.message.customComponentType);
- const providers = ReflectiveInjector.resolve(this.message.providers || []);
+ const componentFactory = this.resolver.resolveComponentFactory(this.instance.customComponentType);
+ const providers = ReflectiveInjector.resolve(this.instance.providers || []);
const injector = ReflectiveInjector.fromResolvedProviders(providers, this.injector);
- this.customComponent = this.customToastHost.createComponent(componentFactory, undefined, injector);
- this.customComponent.instance.message = this.message;
+ this.customComponent = this.toastHost.createComponent(componentFactory, undefined, injector);
}
}
diff --git a/src/modules/toast/toast.module.ts b/src/modules/toast/toast.module.ts
index 5911b12bc..224e5f2ce 100644
--- a/src/modules/toast/toast.module.ts
+++ b/src/modules/toast/toast.module.ts
@@ -13,8 +13,8 @@ import {
SkyToastService
} from './services/toast.service';
import {
- SkyToastContainerComponent
-} from './toast-container.component';
+ SkyToasterComponent
+} from './toaster.component';
import {
SkyToastAdapterService
} from './services/toast-adapter.service';
@@ -31,14 +31,14 @@ export {
@NgModule({
declarations: [
- SkyToastContainerComponent,
+ SkyToasterComponent,
SkyToastComponent
],
imports: [
CommonModule, SkyResourcesModule
],
exports: [
- SkyToastContainerComponent,
+ SkyToasterComponent,
SkyToastComponent
],
providers: [
@@ -46,7 +46,7 @@ export {
SkyToastAdapterService
],
entryComponents: [
- SkyToastContainerComponent,
+ SkyToasterComponent,
SkyToastComponent
]
})
diff --git a/src/modules/toast/toaster.component.html b/src/modules/toast/toaster.component.html
new file mode 100644
index 000000000..86f784c50
--- /dev/null
+++ b/src/modules/toast/toaster.component.html
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/src/modules/toast/toast-container.component.scss b/src/modules/toast/toaster.component.scss
similarity index 100%
rename from src/modules/toast/toast-container.component.scss
rename to src/modules/toast/toaster.component.scss
diff --git a/src/modules/toast/toaster.component.spec.ts b/src/modules/toast/toaster.component.spec.ts
new file mode 100644
index 000000000..9a0810f36
--- /dev/null
+++ b/src/modules/toast/toaster.component.spec.ts
@@ -0,0 +1,49 @@
+import {
+ TestBed
+} from '@angular/core/testing';
+
+import {
+ BehaviorSubject
+} from 'rxjs';
+
+import {
+ SkyToastService,
+ SkyToasterComponent
+} from '.';
+import {
+ SkyToastInstance
+} from './types';
+
+describe('Toaster component', () => {
+ let toastService: SkyToastService;
+ let toastInstances: BehaviorSubject = new BehaviorSubject([]);
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ providers: [
+ {
+ provide: SkyToastService,
+ useValue: {
+ toastInstances: toastInstances
+ }
+ }
+ ]});
+ toastService = TestBed.get(SkyToastService);
+ });
+
+ it('should instantiate a toaster with its own subscription to the toastInstance list',
+ (done: Function) => {
+ let instance: SkyToastInstance = new SkyToastInstance('My message', undefined, 'danger', []);
+ instance.isClosed.subscribe(() => {
+ toastInstances.next([]);
+ });
+ toastInstances.next([instance]);
+
+ let container: SkyToasterComponent = new SkyToasterComponent(toastService);
+ container.ngOnInit();
+ container.toastInstances.subscribe((value) => {
+ expect(value[0]).toBe(instance);
+ done();
+ });
+ });
+});
diff --git a/src/modules/toast/toast-container.component.ts b/src/modules/toast/toaster.component.ts
similarity index 60%
rename from src/modules/toast/toast-container.component.ts
rename to src/modules/toast/toaster.component.ts
index 93e437813..91b3756d2 100644
--- a/src/modules/toast/toast-container.component.ts
+++ b/src/modules/toast/toaster.component.ts
@@ -14,19 +14,19 @@ import {
@Component({
selector: 'sky-toaster',
- templateUrl: './toast-container.component.html',
- styleUrls: ['./toast-container.component.scss'],
+ templateUrl: './toaster.component.html',
+ styleUrls: ['./toaster.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
-export class SkyToastContainerComponent implements OnInit {
+export class SkyToasterComponent implements OnInit {
- public messages: Observable;
+ public toastInstances: Observable;
constructor(
private toast: SkyToastService
) {}
public ngOnInit() {
- this.messages = this.toast.messages;
+ this.toastInstances = this.toast.toastInstances;
}
}
diff --git a/src/modules/toast/types/index.ts b/src/modules/toast/types/index.ts
index 1bb7d3b47..e61e81ac2 100644
--- a/src/modules/toast/types/index.ts
+++ b/src/modules/toast/types/index.ts
@@ -1,3 +1,2 @@
export * from './toast-config';
export * from './toast-instance';
-export * from './toast-type';
diff --git a/src/modules/toast/types/toast-config.ts b/src/modules/toast/types/toast-config.ts
index 96bf785f6..fa4e8922a 100644
--- a/src/modules/toast/types/toast-config.ts
+++ b/src/modules/toast/types/toast-config.ts
@@ -1,15 +1,9 @@
import {
- Type,
- Provider
+ Type
} from '@angular/core';
-import {
- SkyToastType
-} from './toast-type';
-
export interface SkyToastConfig {
message?: string;
customComponentType?: Type;
- providers?: Provider[];
- toastType?: SkyToastType;
+ toastType?: 'info' | 'success' | 'warning' | 'danger';
}
diff --git a/src/modules/toast/types/toast-instance.ts b/src/modules/toast/types/toast-instance.ts
index 11013297d..8682b9233 100644
--- a/src/modules/toast/types/toast-instance.ts
+++ b/src/modules/toast/types/toast-instance.ts
@@ -4,31 +4,18 @@ import {
EventEmitter
} from '@angular/core';
-import {
- Subject
-} from 'rxjs';
-
export class SkyToastInstance {
public isClosed = new EventEmitter();
- public isClosing = new Subject();
+ public isOpen = true;
constructor(
public message: string,
public customComponentType: Type,
- public toastType: string,
- public providers: Provider[] = [],
- private removeFromQueue: Function
+ public toastType: 'info' | 'success' | 'warning' | 'danger',
+ public providers: Provider[] = []
) {}
public close = () => {
- if (!this.isClosing.isStopped) {
- this.isClosing.next();
- this.isClosing.complete();
-
- this.isClosed.subscribe(() => {
- this.removeFromQueue(this);
- this.isClosed.complete();
- });
- }
+ this.isOpen = false;
}
}
diff --git a/src/modules/toast/types/toast-type.ts b/src/modules/toast/types/toast-type.ts
deleted file mode 100644
index 774df702a..000000000
--- a/src/modules/toast/types/toast-type.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-export enum SkyToastType {
- Info,
- Success,
- Warning,
- Danger
-}
From c707c3f4c6553b4795ad2aa683828871ca3485f7 Mon Sep 17 00:00:00 2001
From: Blackbaud-SteveBrush
Date: Wed, 9 May 2018 15:25:24 -0400
Subject: [PATCH 30/46] Adjustments
---
.../toast/toast-custom-demo.component.html | 15 +-
.../toast/toast-custom-demo.component.ts | 10 +-
src/demos/toast/toast-demo.component.html | 25 ++-
src/demos/toast/toast-demo.component.ts | 42 ++++-
src/modules/toast/fixtures/index.ts | 2 +
.../toast/fixtures/toast-fixtures.module.ts | 31 ++++
.../fixtures/toast.component.fixture.html | 5 +
.../toast/fixtures/toast.component.fixture.ts | 27 +++
src/modules/toast/index.ts | 12 +-
src/modules/toast/services/toast.service.ts | 103 -----------
.../toast-adapter.service.spec.ts | 0
.../{services => }/toast-adapter.service.ts | 6 +-
src/modules/toast/toast-body-context.ts | 10 +
src/modules/toast/toast-body.component.html | 3 +
src/modules/toast/toast-body.component.ts | 19 ++
src/modules/toast/toast-instance.ts | 22 +++
src/modules/toast/toast-messages/index.ts | 1 -
.../toast/toast-messages/toast.component.html | 28 ---
.../toast-messages/toast.component.spec.ts | 134 --------------
.../toast/toast-messages/toast.component.ts | 91 ---------
src/modules/toast/toast.component.html | 24 +++
.../{toast-messages => }/toast.component.scss | 5 +-
src/modules/toast/toast.component.spec.ts | 174 ++++++++++++++++++
src/modules/toast/toast.component.ts | 85 +++++++++
src/modules/toast/toast.module.ts | 45 +++--
.../{services => }/toast.service.spec.ts | 4 +-
src/modules/toast/toast.service.ts | 140 ++++++++++++++
src/modules/toast/toast.ts | 45 +++++
src/modules/toast/toaster.component.html | 13 +-
src/modules/toast/toaster.component.scss | 2 +-
src/modules/toast/toaster.component.ts | 66 ++++++-
src/modules/toast/types/index.ts | 2 +-
src/modules/toast/types/toast-config.ts | 10 +-
src/modules/toast/types/toast-instance.ts | 21 ---
src/modules/toast/types/toast-type.ts | 1 +
35 files changed, 763 insertions(+), 460 deletions(-)
create mode 100644 src/modules/toast/fixtures/index.ts
create mode 100644 src/modules/toast/fixtures/toast-fixtures.module.ts
create mode 100644 src/modules/toast/fixtures/toast.component.fixture.html
create mode 100644 src/modules/toast/fixtures/toast.component.fixture.ts
delete mode 100644 src/modules/toast/services/toast.service.ts
rename src/modules/toast/{services => }/toast-adapter.service.spec.ts (100%)
rename src/modules/toast/{services => }/toast-adapter.service.ts (85%)
create mode 100644 src/modules/toast/toast-body-context.ts
create mode 100644 src/modules/toast/toast-body.component.html
create mode 100644 src/modules/toast/toast-body.component.ts
create mode 100644 src/modules/toast/toast-instance.ts
delete mode 100644 src/modules/toast/toast-messages/index.ts
delete mode 100644 src/modules/toast/toast-messages/toast.component.html
delete mode 100644 src/modules/toast/toast-messages/toast.component.spec.ts
delete mode 100644 src/modules/toast/toast-messages/toast.component.ts
create mode 100644 src/modules/toast/toast.component.html
rename src/modules/toast/{toast-messages => }/toast.component.scss (95%)
create mode 100644 src/modules/toast/toast.component.spec.ts
create mode 100644 src/modules/toast/toast.component.ts
rename src/modules/toast/{services => }/toast.service.spec.ts (99%)
create mode 100644 src/modules/toast/toast.service.ts
create mode 100644 src/modules/toast/toast.ts
delete mode 100644 src/modules/toast/types/toast-instance.ts
create mode 100644 src/modules/toast/types/toast-type.ts
diff --git a/src/demos/toast/toast-custom-demo.component.html b/src/demos/toast/toast-custom-demo.component.html
index 510dd3620..6909a9963 100644
--- a/src/demos/toast/toast-custom-demo.component.html
+++ b/src/demos/toast/toast-custom-demo.component.html
@@ -1,8 +1,11 @@
- {{text}}
-
- example.com
-
+ This toast has embedded a custom component for its content. It can even link you to
+ example.com
+
+ Close
+
diff --git a/src/demos/toast/toast-custom-demo.component.ts b/src/demos/toast/toast-custom-demo.component.ts
index ab719c04b..2360292fe 100644
--- a/src/demos/toast/toast-custom-demo.component.ts
+++ b/src/demos/toast/toast-custom-demo.component.ts
@@ -13,9 +13,11 @@ import {
changeDetection: ChangeDetectionStrategy.OnPush
})
export class SkyToastCustomDemoComponent {
- public text = 'This is a templated message. It can even link you to ';
-
constructor(
- public instance: SkyToastInstance
- ) {}
+ private instance: SkyToastInstance
+ ) { }
+
+ public close(): void {
+ this.instance.close();
+ }
}
diff --git a/src/demos/toast/toast-demo.component.html b/src/demos/toast/toast-demo.component.html
index fdb6b7c62..2826e914a 100644
--- a/src/demos/toast/toast-demo.component.html
+++ b/src/demos/toast/toast-demo.component.html
@@ -1,25 +1,36 @@
-
+
- Toast type
+
+ Select toast type:
+
- {{ type }}
+ [(ngModel)]="selectedType"
+ >
+
+ {{ type }}
+
+
+ type="button"
+ (click)="openMessage()"
+ >
Open toast
+
- Open templated toast
+ type="button"
+ (click)="openComponent()"
+ >
+ Open toast with custom component
diff --git a/src/demos/toast/toast-demo.component.ts b/src/demos/toast/toast-demo.component.ts
index d94e0f285..21926aa2d 100644
--- a/src/demos/toast/toast-demo.component.ts
+++ b/src/demos/toast/toast-demo.component.ts
@@ -3,7 +3,8 @@ import {
} from '@angular/core';
import {
- SkyToastService
+ SkyToastService,
+ SkyToastType
} from '../../core';
import {
@@ -15,18 +16,41 @@ import {
templateUrl: './toast-demo.component.html'
})
export class SkyToastDemoComponent {
- public selectedType: 'info' | 'success' | 'warning' | 'danger' = 'info';
- public types = ['info', 'success', 'warning', 'danger'];
+ public selectedType: SkyToastType = 'info';
+ public types: SkyToastType[] = [
+ 'info',
+ 'success',
+ 'warning',
+ 'danger'
+ ];
constructor(
- private toastSvc: SkyToastService
- ) {}
+ private toastService: SkyToastService
+ ) { }
- public openMessage() {
- this.toastSvc.openMessage('This is a ' + this.selectedType + ' toast.', {toastType: this.selectedType});
+ public openMessage(): void {
+ const instance = this.toastService.openMessage(
+ `This is a ${this.selectedType} toast.`,
+ {
+ type: this.selectedType
+ }
+ );
+
+ instance.closed.subscribe(() => {
+ console.log('Message toast closed!');
+ });
}
- public openTemplatedMessage() {
- this.toastSvc.openTemplatedMessage(SkyToastCustomDemoComponent, {toastType: this.selectedType});
+ public openComponent(): void {
+ const instance = this.toastService.openComponent(
+ SkyToastCustomDemoComponent,
+ {
+ type: this.selectedType
+ }
+ );
+
+ instance.closed.subscribe(() => {
+ console.log('Custom component toast closed!');
+ });
}
}
diff --git a/src/modules/toast/fixtures/index.ts b/src/modules/toast/fixtures/index.ts
new file mode 100644
index 000000000..009045a31
--- /dev/null
+++ b/src/modules/toast/fixtures/index.ts
@@ -0,0 +1,2 @@
+export * from './toast-fixtures.module';
+export * from './toast.component.fixture';
diff --git a/src/modules/toast/fixtures/toast-fixtures.module.ts b/src/modules/toast/fixtures/toast-fixtures.module.ts
new file mode 100644
index 000000000..92be37bab
--- /dev/null
+++ b/src/modules/toast/fixtures/toast-fixtures.module.ts
@@ -0,0 +1,31 @@
+// #region imports
+import {
+ NgModule
+} from '@angular/core';
+
+import {
+ CommonModule
+} from '@angular/common';
+
+import {
+ SkyToastModule
+} from '../toast.module';
+
+import {
+ SkyToastTestComponent
+} from './toast.component.fixture';
+// #endregion
+
+@NgModule({
+ declarations: [
+ SkyToastTestComponent
+ ],
+ imports: [
+ CommonModule,
+ SkyToastModule
+ ],
+ exports: [
+ SkyToastTestComponent
+ ]
+})
+export class SkyToastFixturesModule { }
diff --git a/src/modules/toast/fixtures/toast.component.fixture.html b/src/modules/toast/fixtures/toast.component.fixture.html
new file mode 100644
index 000000000..d0ccffc10
--- /dev/null
+++ b/src/modules/toast/fixtures/toast.component.fixture.html
@@ -0,0 +1,5 @@
+
+ Inner content here...
+
diff --git a/src/modules/toast/fixtures/toast.component.fixture.ts b/src/modules/toast/fixtures/toast.component.fixture.ts
new file mode 100644
index 000000000..221f5b726
--- /dev/null
+++ b/src/modules/toast/fixtures/toast.component.fixture.ts
@@ -0,0 +1,27 @@
+// #region imports
+import {
+ Component,
+ ViewChild
+} from '@angular/core';
+
+import {
+ SkyToastType
+} from '../types/toast-type';
+
+import {
+ SkyToastComponent
+} from '../toast.component';
+// #endregion
+
+@Component({
+ selector: 'sky-test-cmp',
+ templateUrl: './toast.component.fixture.html'
+})
+export class SkyToastTestComponent {
+ public toastType: SkyToastType;
+
+ @ViewChild(SkyToastComponent)
+ public toastComponent: SkyToastComponent;
+
+ public onClosed(): void {}
+}
diff --git a/src/modules/toast/index.ts b/src/modules/toast/index.ts
index c5d09997b..5148a8060 100644
--- a/src/modules/toast/index.ts
+++ b/src/modules/toast/index.ts
@@ -1,10 +1,4 @@
-export {
- SkyToasterComponent
-} from './toaster.component';
-export {
- SkyToastService
-} from './services/toast.service';
-export {
- SkyToastModule
-} from './toast.module';
export * from './types';
+export * from './toast-instance';
+export * from './toast.module';
+export * from './toast.service';
diff --git a/src/modules/toast/services/toast.service.ts b/src/modules/toast/services/toast.service.ts
deleted file mode 100644
index 43085af79..000000000
--- a/src/modules/toast/services/toast.service.ts
+++ /dev/null
@@ -1,103 +0,0 @@
-import {
- Injectable,
- ComponentRef,
- ComponentFactoryResolver,
- Injector,
- ApplicationRef,
- EmbeddedViewRef,
- OnDestroy,
- Type,
- Provider
-} from '@angular/core';
-
-import {
- BehaviorSubject
-} from 'rxjs';
-
-import {
- SkyToasterComponent
-} from '../toaster.component';
-import {
- SkyToastAdapterService
-} from './toast-adapter.service';
-import {
- SkyToastInstance,
- SkyToastConfig
-} from '../types';
-
-@Injectable()
-export class SkyToastService implements OnDestroy {
- private host: ComponentRef
;
-
- private _toastInstances: SkyToastInstance[] = [];
- public toastInstances: BehaviorSubject = new BehaviorSubject([]);
-
- constructor(
- private appRef: ApplicationRef,
- private injector: Injector,
- private resolver: ComponentFactoryResolver,
- private adapter: SkyToastAdapterService
- ) {}
-
- public openMessage(message: string, config: SkyToastConfig = {}): SkyToastInstance {
- return this.open(config, message);
- }
-
- public openTemplatedMessage(customComponentType: Type, config: SkyToastConfig = {}, providers?: Provider[]): SkyToastInstance {
- return this.open(config, undefined, customComponentType, providers);
- }
-
- public ngOnDestroy() {
- this.host = undefined;
- this._toastInstances.forEach(instance => {
- instance.close();
- });
- this.toastInstances.next([]);
- this.adapter.removeHostElement();
- }
-
- private open(config: SkyToastConfig, message?: string, customComponentType?: Type, providers?: Provider[]): SkyToastInstance {
- if (!this.host) {
- this.host = this.createHostComponent();
- }
-
- let instance: SkyToastInstance = this.createToastInstance(config, message, customComponentType, providers);
- this._toastInstances.push(instance);
- this.toastInstances.next(this._toastInstances);
-
- return instance;
- }
-
- private removeFromQueue: Function = (instance: SkyToastInstance) => {
- this._toastInstances = this._toastInstances.filter(inst => inst !== instance);
- this.toastInstances.next(this._toastInstances);
- }
-
- private createToastInstance(
- config: SkyToastConfig,
- message?: string,
- customComponentType?: Type,
- providers?: Provider[]
- ): SkyToastInstance {
- let newToast = new SkyToastInstance(
- message,
- customComponentType,
- config.toastType ? config.toastType : 'info',
- providers);
- newToast.isClosed.subscribe(() => { this.removeFromQueue(newToast); });
- return newToast;
- }
-
- private createHostComponent(): ComponentRef {
- const componentRef = this.resolver
- .resolveComponentFactory(SkyToasterComponent)
- .create(this.injector);
-
- const domElem = (componentRef.hostView as EmbeddedViewRef).rootNodes[0];
-
- this.appRef.attachView(componentRef.hostView);
- this.adapter.appendToBody(domElem);
-
- return componentRef;
- }
-}
diff --git a/src/modules/toast/services/toast-adapter.service.spec.ts b/src/modules/toast/toast-adapter.service.spec.ts
similarity index 100%
rename from src/modules/toast/services/toast-adapter.service.spec.ts
rename to src/modules/toast/toast-adapter.service.spec.ts
diff --git a/src/modules/toast/services/toast-adapter.service.ts b/src/modules/toast/toast-adapter.service.ts
similarity index 85%
rename from src/modules/toast/services/toast-adapter.service.ts
rename to src/modules/toast/toast-adapter.service.ts
index b3f2e08b5..99d6aa68a 100644
--- a/src/modules/toast/services/toast-adapter.service.ts
+++ b/src/modules/toast/toast-adapter.service.ts
@@ -1,3 +1,4 @@
+// #region imports
import {
Injectable,
Renderer2,
@@ -6,7 +7,8 @@ import {
import {
SkyWindowRefService
-} from '../../window';
+} from '../window';
+// #endregion
@Injectable()
export class SkyToastAdapterService {
@@ -26,7 +28,7 @@ export class SkyToastAdapterService {
public removeHostElement(): void {
const document = this.windowRef.getWindow().document;
- const hostElement = document.querySelector('sky-toast');
+ const hostElement = document.querySelector('sky-toaster');
this.renderer.removeChild(document.body, hostElement);
}
}
diff --git a/src/modules/toast/toast-body-context.ts b/src/modules/toast/toast-body-context.ts
new file mode 100644
index 000000000..2f3873ef8
--- /dev/null
+++ b/src/modules/toast/toast-body-context.ts
@@ -0,0 +1,10 @@
+// #region imports
+import {
+ Injectable
+} from '@angular/core';
+// #endregion
+
+@Injectable()
+export class SkyToastBodyContext {
+ public message: string;
+}
diff --git a/src/modules/toast/toast-body.component.html b/src/modules/toast/toast-body.component.html
new file mode 100644
index 000000000..813de5501
--- /dev/null
+++ b/src/modules/toast/toast-body.component.html
@@ -0,0 +1,3 @@
+
+ {{ context.message }}
+
diff --git a/src/modules/toast/toast-body.component.ts b/src/modules/toast/toast-body.component.ts
new file mode 100644
index 000000000..d9d025765
--- /dev/null
+++ b/src/modules/toast/toast-body.component.ts
@@ -0,0 +1,19 @@
+// #region imports
+import {
+ Component
+} from '@angular/core';
+
+import {
+ SkyToastBodyContext
+} from './toast-body-context';
+// #endregion
+
+@Component({
+ selector: 'sky-toast-body',
+ templateUrl: './toast-body.component.html'
+})
+export class SkyToastBodyComponent {
+ constructor(
+ public context: SkyToastBodyContext
+ ) { }
+}
diff --git a/src/modules/toast/toast-instance.ts b/src/modules/toast/toast-instance.ts
new file mode 100644
index 000000000..6976e6134
--- /dev/null
+++ b/src/modules/toast/toast-instance.ts
@@ -0,0 +1,22 @@
+// #region imports
+import {
+ EventEmitter
+} from '@angular/core';
+
+import {
+ Observable
+} from 'rxjs/Observable';
+// #endregion
+
+export class SkyToastInstance {
+ public get closed(): Observable {
+ return this._closed;
+ }
+
+ private _closed = new EventEmitter();
+
+ public close() {
+ this._closed.emit();
+ this._closed.complete();
+ }
+}
diff --git a/src/modules/toast/toast-messages/index.ts b/src/modules/toast/toast-messages/index.ts
deleted file mode 100644
index 6502de796..000000000
--- a/src/modules/toast/toast-messages/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from './toast.component';
diff --git a/src/modules/toast/toast-messages/toast.component.html b/src/modules/toast/toast-messages/toast.component.html
deleted file mode 100644
index 27bed1a62..000000000
--- a/src/modules/toast/toast-messages/toast.component.html
+++ /dev/null
@@ -1,28 +0,0 @@
-
-
-
-
- {{instance.message}}
-
-
-
-
-
-
-
-
-
-
diff --git a/src/modules/toast/toast-messages/toast.component.spec.ts b/src/modules/toast/toast-messages/toast.component.spec.ts
deleted file mode 100644
index f2b96fea5..000000000
--- a/src/modules/toast/toast-messages/toast.component.spec.ts
+++ /dev/null
@@ -1,134 +0,0 @@
-import {
- ComponentFactoryResolver,
- Injector
-} from '@angular/core';
-import {
- TestBed
-} from '@angular/core/testing';
-
-import {
- SkyToastInstance
-} from '../types';
-import {
- SkyToastComponent
-} from '.';
-
-describe('Toast component', () => {
- class TestComponent { constructor(public instance: SkyToastInstance) {} }
- let instance: SkyToastInstance;
-
- beforeEach(() => {
- TestBed.configureTestingModule({
- providers: [
- Injector,
- {
- provide: ComponentFactoryResolver,
- useValue: {
- resolveComponentFactory() {
- return {
- create() {
- return {
- destroy() {},
- hostView: {
- rootNodes: [
- {}
- ]
- },
- instance: {
- messageStream: {
- take() {
- return {
- subscribe() { }
- };
- },
- next() {}
- },
- attach() {
- return {
- close() { },
- closed: {
- take() {
- return {
- subscribe() { }
- };
- }
- }
- };
- }
- }
- };
- }
- };
- }
- }
- }
- ]
- });
- });
-
- it('should instantiate a toast without a custom component',
- () => {
- instance = new SkyToastInstance('My message', undefined, 'danger', []);
- let toast: SkyToastComponent;
- try {
- toast = new SkyToastComponent(undefined, undefined);
- toast.instance = instance;
-
- toast.ngOnInit();
- expect((toast as any).customComponent).toBeFalsy();
-
- toast.ngOnDestroy();
- expect((toast as any).customComponent).toBeFalsy();
- } catch (error) {
- fail();
- }
- expect(toast).toBeTruthy();
- });
-
- it('should show proper closed or open states', () => {
- instance = new SkyToastInstance('My message', undefined, 'danger', []);
- let toast: SkyToastComponent = new SkyToastComponent(undefined, undefined);
- toast.instance = instance;
-
- toast.ngOnInit();
-
- expect(toast.getAnimationState()).toBe('toastOpen');
- instance.close();
- toast.animationDone(undefined);
- expect(toast.getAnimationState()).toBe('toastClosed');
- expect(toast).toBeTruthy();
- });
-
- it('should instantiate a toast with a custom component and tear it down',
- () => {
- let clearCalled: boolean = false;
- let createComponentCalled: boolean = false;
- let destroyCalled: boolean = false;
-
- instance = new SkyToastInstance(undefined, TestComponent, 'danger', []);
-
- let toast: SkyToastComponent;
- toast = new SkyToastComponent(TestBed.get(ComponentFactoryResolver), TestBed.get(Injector));
- toast.instance = instance;
-
- (toast as any).toastHost = {
- clear: () => { clearCalled = true; },
- createComponent: () => {
- createComponentCalled = true;
- return {
- instance: {},
- destroy: () => { destroyCalled = true; }
- };
- }
- };
-
- toast.ngOnInit();
- expect((toast as any).customComponent).toBeTruthy();
- expect(clearCalled).toBeTruthy();
- expect(createComponentCalled).toBeTruthy();
-
- toast.ngOnDestroy();
- expect(destroyCalled).toBeTruthy();
- expect(toast).toBeTruthy();
- });
-});
diff --git a/src/modules/toast/toast-messages/toast.component.ts b/src/modules/toast/toast-messages/toast.component.ts
deleted file mode 100644
index dddf1a317..000000000
--- a/src/modules/toast/toast-messages/toast.component.ts
+++ /dev/null
@@ -1,91 +0,0 @@
-import {
- Component,
- Input,
- ComponentFactoryResolver,
- OnInit,
- ViewChild,
- OnDestroy,
- ViewContainerRef,
- ReflectiveInjector,
- ComponentRef,
- Injector,
- trigger,
- state,
- style,
- animate,
- transition,
- ChangeDetectionStrategy
-} from '@angular/core';
-
-import {
- SkyToastInstance
-} from '../types';
-
-const TOAST_OPEN_STATE = 'toastOpen';
-const TOAST_CLOSED_STATE = 'toastClosed';
-
-@Component({
- selector: 'sky-toast',
- templateUrl: './toast.component.html',
- styleUrls: ['./toast.component.scss'],
- animations: [
- trigger('toastState', [
- state(TOAST_OPEN_STATE, style({ opacity: 1 })),
- state(TOAST_CLOSED_STATE, style({ opacity: 0 })),
- transition(`toastOpen => toastClosed`, animate('500ms linear'))
- ])
- ],
- changeDetection: ChangeDetectionStrategy.OnPush
-})
-export class SkyToastComponent implements OnInit, OnDestroy {
- @Input('instance')
- public instance: SkyToastInstance;
-
- @ViewChild('skytoastcustomtemplate', { read: ViewContainerRef })
- private toastHost: ViewContainerRef;
-
- private customComponent: ComponentRef;
-
- constructor(
- private resolver: ComponentFactoryResolver,
- private injector: Injector
- ) {}
-
- public getAnimationState(): string {
- return this.instance.isOpen ? TOAST_OPEN_STATE : TOAST_CLOSED_STATE;
- }
-
- public ngOnInit() {
- if (this.instance.customComponentType) {
- this.loadComponent();
- }
- }
-
- public ngOnDestroy() {
- if (this.customComponent) {
- this.customComponent.destroy();
- }
- }
-
- public animationDone(event: AnimationEvent) {
- if (!this.instance.isOpen) {
- this.instance.isClosed.emit();
- this.instance.isClosed.complete();
- }
- }
-
- private loadComponent() {
- this.toastHost.clear();
- this.instance.providers.push({
- provide: SkyToastInstance,
- useValue: this.instance
- });
-
- const componentFactory = this.resolver.resolveComponentFactory(this.instance.customComponentType);
- const providers = ReflectiveInjector.resolve(this.instance.providers || []);
-
- const injector = ReflectiveInjector.fromResolvedProviders(providers, this.injector);
-
- this.customComponent = this.toastHost.createComponent(componentFactory, undefined, injector);
- }
-}
diff --git a/src/modules/toast/toast.component.html b/src/modules/toast/toast.component.html
new file mode 100644
index 000000000..c39b0a5e6
--- /dev/null
+++ b/src/modules/toast/toast.component.html
@@ -0,0 +1,24 @@
+
diff --git a/src/modules/toast/toast-messages/toast.component.scss b/src/modules/toast/toast.component.scss
similarity index 95%
rename from src/modules/toast/toast-messages/toast.component.scss
rename to src/modules/toast/toast.component.scss
index df2d48442..9c7fda9db 100644
--- a/src/modules/toast/toast-messages/toast.component.scss
+++ b/src/modules/toast/toast.component.scss
@@ -1,5 +1,4 @@
-@import '../../../scss/variables';
-@import "../../../scss/mixins";
+@import '../../scss/mixins';
.sky-toast {
padding: 0 $sky-padding;
@@ -107,6 +106,6 @@
display: block;
&:hover {
- opacity: 1.0;
+ opacity: 1;
}
}
diff --git a/src/modules/toast/toast.component.spec.ts b/src/modules/toast/toast.component.spec.ts
new file mode 100644
index 000000000..77ef2d718
--- /dev/null
+++ b/src/modules/toast/toast.component.spec.ts
@@ -0,0 +1,174 @@
+// #region imports
+import {
+ ComponentFixture,
+ TestBed
+} from '@angular/core/testing';
+
+import {
+ expect
+} from '@blackbaud/skyux-builder/runtime/testing/browser';
+
+import {
+ SkyToastFixturesModule,
+ SkyToastTestComponent
+} from './fixtures';
+// #endregion
+
+fdescribe('Toast component', () => {
+ let fixture: ComponentFixture;
+ let component: SkyToastTestComponent;
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [
+ SkyToastFixturesModule
+ ]
+ });
+
+ fixture = TestBed.createComponent(SkyToastTestComponent);
+ component = fixture.componentInstance;
+ });
+
+ afterEach(() => {
+ fixture.destroy();
+ });
+
+ it('should set defaults', () => {
+ expect(component.toastComponent.toastType).toEqual('info');
+ });
+});
+
+// import {
+// ComponentFactoryResolver,
+// Injector
+// } from '@angular/core';
+// import {
+// TestBed
+// } from '@angular/core/testing';
+
+// import {
+// SkyToastInstance
+// } from '../types';
+// import {
+// SkyToastComponent
+// } from '.';
+
+// describe('Toast component', () => {
+// class TestComponent { constructor(public instance: SkyToastInstance) {} }
+// let instance: SkyToastInstance;
+
+// beforeEach(() => {
+// TestBed.configureTestingModule({
+// providers: [
+// Injector,
+// {
+// provide: ComponentFactoryResolver,
+// useValue: {
+// resolveComponentFactory() {
+// return {
+// create() {
+// return {
+// destroy() {},
+// hostView: {
+// rootNodes: [
+// {}
+// ]
+// },
+// instance: {
+// messageStream: {
+// take() {
+// return {
+// subscribe() { }
+// };
+// },
+// next() {}
+// },
+// attach() {
+// return {
+// close() { },
+// closed: {
+// take() {
+// return {
+// subscribe() { }
+// };
+// }
+// }
+// };
+// }
+// }
+// };
+// }
+// };
+// }
+// }
+// }
+// ]
+// });
+// });
+
+// it('should instantiate a toast without a custom component',
+// () => {
+// instance = new SkyToastInstance('My message', undefined, 'danger', []);
+// let toast: SkyToastComponent;
+// try {
+// toast = new SkyToastComponent(undefined, undefined);
+// toast.instance = instance;
+
+// toast.ngOnInit();
+// expect((toast as any).customComponent).toBeFalsy();
+
+// toast.ngOnDestroy();
+// expect((toast as any).customComponent).toBeFalsy();
+// } catch (error) {
+// fail();
+// }
+// expect(toast).toBeTruthy();
+// });
+
+// it('should show proper closed or open states', () => {
+// instance = new SkyToastInstance('My message', undefined, 'danger', []);
+// let toast: SkyToastComponent = new SkyToastComponent(undefined, undefined);
+// toast.instance = instance;
+
+// toast.ngOnInit();
+
+// expect(toast.getAnimationState()).toBe('toastOpen');
+// instance.close();
+// toast.animationDone(undefined);
+// expect(toast.getAnimationState()).toBe('toastClosed');
+// expect(toast).toBeTruthy();
+// });
+
+// it('should instantiate a toast with a custom component and tear it down',
+// () => {
+// let clearCalled: boolean = false;
+// let createComponentCalled: boolean = false;
+// let destroyCalled: boolean = false;
+
+// instance = new SkyToastInstance(undefined, TestComponent, 'danger', []);
+
+// let toast: SkyToastComponent;
+// toast = new SkyToastComponent(TestBed.get(ComponentFactoryResolver), TestBed.get(Injector));
+// toast.instance = instance;
+
+// (toast as any).toastHost = {
+// clear: () => { clearCalled = true; },
+// createComponent: () => {
+// createComponentCalled = true;
+// return {
+// instance: {},
+// destroy: () => { destroyCalled = true; }
+// };
+// }
+// };
+
+// toast.ngOnInit();
+// expect((toast as any).customComponent).toBeTruthy();
+// expect(clearCalled).toBeTruthy();
+// expect(createComponentCalled).toBeTruthy();
+
+// toast.ngOnDestroy();
+// expect(destroyCalled).toBeTruthy();
+// expect(toast).toBeTruthy();
+// });
+// });
diff --git a/src/modules/toast/toast.component.ts b/src/modules/toast/toast.component.ts
new file mode 100644
index 000000000..9d9bd513b
--- /dev/null
+++ b/src/modules/toast/toast.component.ts
@@ -0,0 +1,85 @@
+// #region imports
+import {
+ ChangeDetectionStrategy,
+ ChangeDetectorRef,
+ Component,
+ EventEmitter,
+ Input,
+ OnInit,
+ Output
+} from '@angular/core';
+
+import {
+ trigger,
+ state,
+ style,
+ animate,
+ transition,
+ AnimationEvent
+} from '@angular/animations';
+
+import {
+ SkyToastType
+} from './types';
+// #endregion
+
+const TOAST_OPEN_STATE = 'toastOpen';
+const TOAST_CLOSED_STATE = 'toastClosed';
+
+@Component({
+ selector: 'sky-toast',
+ templateUrl: './toast.component.html',
+ styleUrls: ['./toast.component.scss'],
+ animations: [
+ trigger('toastState', [
+ state(TOAST_OPEN_STATE, style({ opacity: 1 })),
+ state(TOAST_CLOSED_STATE, style({ opacity: 0 })),
+ transition(
+ `${TOAST_OPEN_STATE} => ${TOAST_CLOSED_STATE}`,
+ animate('150ms linear')
+ )
+ ])
+ ],
+ changeDetection: ChangeDetectionStrategy.OnPush
+})
+export class SkyToastComponent implements OnInit {
+ @Input()
+ public set toastType(value: SkyToastType) {
+ this._toastType = value;
+ }
+
+ public get toastType(): SkyToastType {
+ return this._toastType || 'info';
+ }
+
+ @Output()
+ public closed = new EventEmitter();
+
+ private isOpen = false;
+
+ private _toastType: SkyToastType;
+
+ constructor(
+ private changeDetector: ChangeDetectorRef
+ ) { }
+
+ public ngOnInit(): void {
+ this.isOpen = true;
+ }
+
+ public getAnimationState(): string {
+ return (this.isOpen) ? TOAST_OPEN_STATE : TOAST_CLOSED_STATE;
+ }
+
+ public onAnimationDone(event: AnimationEvent) {
+ if (event.toState === TOAST_CLOSED_STATE) {
+ this.closed.emit();
+ this.closed.complete();
+ }
+ }
+
+ public close() {
+ this.isOpen = false;
+ this.changeDetector.markForCheck();
+ }
+}
diff --git a/src/modules/toast/toast.module.ts b/src/modules/toast/toast.module.ts
index 224e5f2ce..44127183c 100644
--- a/src/modules/toast/toast.module.ts
+++ b/src/modules/toast/toast.module.ts
@@ -1,6 +1,8 @@
+// #region imports
import {
NgModule
} from '@angular/core';
+
import {
CommonModule
} from '@angular/common';
@@ -9,36 +11,38 @@ import {
SkyResourcesModule
} from '../resources';
-import {
- SkyToastService
-} from './services/toast.service';
-import {
- SkyToasterComponent
-} from './toaster.component';
import {
SkyToastAdapterService
-} from './services/toast-adapter.service';
+} from './toast-adapter.service';
+
+import {
+ SkyToastBodyComponent
+} from './toast-body.component';
+
import {
SkyToastComponent
-} from './toast-messages';
+} from './toast.component';
+
+import {
+ SkyToasterComponent
+} from './toaster.component';
-export {
- SkyToastInstance
-} from './types';
-export {
+import {
SkyToastService
-};
+} from './toast.service';
+// #endregion
@NgModule({
declarations: [
- SkyToasterComponent,
- SkyToastComponent
+ SkyToastBodyComponent,
+ SkyToastComponent,
+ SkyToasterComponent
],
imports: [
- CommonModule, SkyResourcesModule
+ CommonModule,
+ SkyResourcesModule
],
exports: [
- SkyToasterComponent,
SkyToastComponent
],
providers: [
@@ -46,8 +50,9 @@ export {
SkyToastAdapterService
],
entryComponents: [
- SkyToasterComponent,
- SkyToastComponent
+ SkyToastBodyComponent,
+ SkyToastComponent,
+ SkyToasterComponent
]
})
-export class SkyToastModule {}
+export class SkyToastModule { }
diff --git a/src/modules/toast/services/toast.service.spec.ts b/src/modules/toast/toast.service.spec.ts
similarity index 99%
rename from src/modules/toast/services/toast.service.spec.ts
rename to src/modules/toast/toast.service.spec.ts
index a3614428d..28d01e0ae 100644
--- a/src/modules/toast/services/toast.service.spec.ts
+++ b/src/modules/toast/toast.service.spec.ts
@@ -9,7 +9,7 @@ import {
import {
SkyWindowRefService
-} from '../../window';
+} from '../window';
import {
SkyToastService
@@ -19,7 +19,7 @@ import {
} from './toast-adapter.service';
import {
SkyToastInstance
-} from '../types';
+} from './toast-instance';
describe('Toast service', () => {
class TestComponent { constructor(public message: SkyToastInstance) { } }
diff --git a/src/modules/toast/toast.service.ts b/src/modules/toast/toast.service.ts
new file mode 100644
index 000000000..1edee5132
--- /dev/null
+++ b/src/modules/toast/toast.service.ts
@@ -0,0 +1,140 @@
+// #region imports
+import {
+ ApplicationRef,
+ ComponentRef,
+ ComponentFactoryResolver,
+ EmbeddedViewRef,
+ Injectable,
+ Injector,
+ OnDestroy,
+ Provider
+} from '@angular/core';
+
+import {
+ BehaviorSubject
+} from 'rxjs/BehaviorSubject';
+
+import {
+ Observable
+} from 'rxjs/Observable';
+
+import {
+ SkyToast
+} from './toast';
+
+import {
+ SkyToastConfig
+} from './types';
+
+import {
+ SkyToastAdapterService
+} from './toast-adapter.service';
+
+import {
+ SkyToastBodyComponent
+} from './toast-body.component';
+
+import {
+ SkyToastInstance
+} from './toast-instance';
+
+import {
+ SkyToastBodyContext
+} from './toast-body-context';
+
+import {
+ SkyToasterComponent
+} from './toaster.component';
+// #endregion
+
+@Injectable()
+export class SkyToastService implements OnDestroy {
+ public get toastStream(): Observable {
+ return this._toastStream;
+ }
+
+ private host: ComponentRef;
+ private toasts: SkyToast[] = [];
+
+ private _toastStream = new BehaviorSubject([]);
+
+ constructor(
+ private appRef: ApplicationRef,
+ private resolver: ComponentFactoryResolver,
+ private injector: Injector,
+ private adapter: SkyToastAdapterService
+ ) { }
+
+ public ngOnDestroy() {
+ this.host = undefined;
+ this.toasts.forEach(toast => toast.instance.close());
+ this.toasts = [];
+ this._toastStream.next(this.toasts);
+ this._toastStream.complete();
+ this.adapter.removeHostElement();
+ }
+
+ public openMessage(
+ message: string,
+ config?: SkyToastConfig
+ ): SkyToastInstance {
+ const context = new SkyToastBodyContext();
+ context.message = message;
+
+ const providers = [{
+ provide: SkyToastBodyContext,
+ useValue: context
+ }];
+
+ return this.openComponent(SkyToastBodyComponent, config, providers);
+ }
+
+ public openComponent(
+ component: any,
+ config?: SkyToastConfig,
+ providers: Provider[] = []
+ ): SkyToastInstance {
+ if (!this.host) {
+ this.host = this.createHostComponent();
+ }
+
+ const instance = new SkyToastInstance();
+
+ providers.push({
+ provide: SkyToastInstance,
+ useValue: instance
+ });
+
+ const toast = new SkyToast(component, providers, config);
+ toast.instance = instance;
+ this.addToast(toast);
+
+ return instance;
+ }
+
+ private addToast(toast: SkyToast): void {
+ this.toasts.push(toast);
+ this._toastStream.next(this.toasts);
+ toast.instance.closed.subscribe(() => {
+ this.removeToast(toast);
+ });
+ }
+
+ private removeToast(toast: SkyToast): void {
+ this.toasts = this.toasts.filter(t => t !== toast);
+ this._toastStream.next(this.toasts);
+ }
+
+ private createHostComponent(): ComponentRef {
+ const componentRef = this.resolver
+ .resolveComponentFactory(SkyToasterComponent)
+ .create(this.injector);
+
+ const domElem = (componentRef.hostView as EmbeddedViewRef).rootNodes[0];
+
+ this.appRef.attachView(componentRef.hostView);
+ this.adapter.appendToBody(domElem);
+
+ return componentRef;
+ }
+}
diff --git a/src/modules/toast/toast.ts b/src/modules/toast/toast.ts
new file mode 100644
index 000000000..9eb016674
--- /dev/null
+++ b/src/modules/toast/toast.ts
@@ -0,0 +1,45 @@
+// #region imports
+import {
+ Provider
+} from '@angular/core';
+
+import {
+ SkyToastConfig
+} from './types';
+
+import {
+ SkyToastInstance
+} from './toast-instance';
+// #endregion
+
+export class SkyToast {
+ public get bodyComponent(): any {
+ return this._bodyComponent;
+ }
+
+ public get bodyComponentProviders(): Provider[] {
+ return this._bodyComponentProviders;
+ }
+
+ public get config(): SkyToastConfig {
+ return this._config;
+ }
+
+ public get instance(): SkyToastInstance {
+ return this._instance;
+ }
+
+ public set instance(value: SkyToastInstance) {
+ if (!this._instance) {
+ this._instance = value;
+ }
+ }
+
+ private _instance: SkyToastInstance;
+
+ constructor(
+ private _bodyComponent: any,
+ private _bodyComponentProviders: Provider[],
+ private _config: SkyToastConfig
+ ) { }
+}
diff --git a/src/modules/toast/toaster.component.html b/src/modules/toast/toaster.component.html
index 86f784c50..86b3a5e85 100644
--- a/src/modules/toast/toaster.component.html
+++ b/src/modules/toast/toaster.component.html
@@ -1,7 +1,14 @@
+ class="sky-toaster"
+>
+ *ngFor="let toast of toastStream | async"
+ [toastType]="toast.config.type"
+ (closed)="onToastClosed(toast)"
+ >
+
+
diff --git a/src/modules/toast/toaster.component.scss b/src/modules/toast/toaster.component.scss
index ca972f51b..3cd01dfc4 100644
--- a/src/modules/toast/toaster.component.scss
+++ b/src/modules/toast/toaster.component.scss
@@ -1,4 +1,4 @@
-@import '../../scss/variables';
+@import '../../scss/mixins';
.sky-toaster {
bottom: 0;
diff --git a/src/modules/toast/toaster.component.ts b/src/modules/toast/toaster.component.ts
index 91b3756d2..ca57fd02a 100644
--- a/src/modules/toast/toaster.component.ts
+++ b/src/modules/toast/toaster.component.ts
@@ -1,16 +1,30 @@
+// #region imports
import {
+ AfterViewInit,
+ ChangeDetectionStrategy,
Component,
- OnInit,
- ChangeDetectionStrategy
+ ComponentFactoryResolver,
+ Injector,
+ ReflectiveInjector,
+ QueryList,
+ ViewChildren,
+ ViewContainerRef
} from '@angular/core';
import {
Observable
-} from 'rxjs';
+} from 'rxjs/Observable';
+
+import 'rxjs/add/operator/take';
+
+import {
+ SkyToast
+} from './toast';
import {
SkyToastService
-} from './services/toast.service';
+} from './toast.service';
+// #endregion
@Component({
selector: 'sky-toaster',
@@ -18,15 +32,47 @@ import {
styleUrls: ['./toaster.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
-export class SkyToasterComponent implements OnInit {
+export class SkyToasterComponent implements AfterViewInit {
+ public get toastStream(): Observable {
+ return this.toastService.toastStream;
+ }
- public toastInstances: Observable;
+ @ViewChildren('toastContent', { read: ViewContainerRef })
+ private toastContent: QueryList;
constructor(
- private toast: SkyToastService
- ) {}
+ private toastService: SkyToastService,
+ private resolver: ComponentFactoryResolver,
+ private injector: Injector
+ ) { }
+
+ public ngAfterViewInit(): void {
+ this.injectToastContent();
+ this.toastContent.changes.subscribe(() => {
+ this.injectToastContent();
+ });
+ }
+
+ public onToastClosed(toast: SkyToast): void {
+ toast.instance.close();
+ }
+
+ private injectToastContent(): void {
+ // Dynamically inject each toast's body content when the number of toasts changes.
+ this.toastService.toastStream.take(1).subscribe((toasts) => {
+ this.toastContent.toArray().forEach((target: ViewContainerRef, i: number) => {
+ target.clear();
+
+ const toast = toasts[i];
+ const componentFactory = this.resolver.resolveComponentFactory(toast.bodyComponent);
+ const injector = ReflectiveInjector.fromResolvedProviders(
+ ReflectiveInjector.resolve(toast.bodyComponentProviders),
+ this.injector
+ );
- public ngOnInit() {
- this.toastInstances = this.toast.toastInstances;
+ const componentRef = target.createComponent(componentFactory, undefined, injector);
+ componentRef.changeDetectorRef.detectChanges();
+ });
+ });
}
}
diff --git a/src/modules/toast/types/index.ts b/src/modules/toast/types/index.ts
index e61e81ac2..8a3475bf7 100644
--- a/src/modules/toast/types/index.ts
+++ b/src/modules/toast/types/index.ts
@@ -1,2 +1,2 @@
export * from './toast-config';
-export * from './toast-instance';
+export * from './toast-type';
diff --git a/src/modules/toast/types/toast-config.ts b/src/modules/toast/types/toast-config.ts
index fa4e8922a..179e1d11c 100644
--- a/src/modules/toast/types/toast-config.ts
+++ b/src/modules/toast/types/toast-config.ts
@@ -1,9 +1,9 @@
+// #region imports
import {
- Type
-} from '@angular/core';
+ SkyToastType
+} from './toast-type';
+// #endregion
export interface SkyToastConfig {
- message?: string;
- customComponentType?: Type;
- toastType?: 'info' | 'success' | 'warning' | 'danger';
+ type?: SkyToastType;
}
diff --git a/src/modules/toast/types/toast-instance.ts b/src/modules/toast/types/toast-instance.ts
deleted file mode 100644
index 8682b9233..000000000
--- a/src/modules/toast/types/toast-instance.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import {
- Type,
- Provider,
- EventEmitter
-} from '@angular/core';
-
-export class SkyToastInstance {
- public isClosed = new EventEmitter();
- public isOpen = true;
-
- constructor(
- public message: string,
- public customComponentType: Type,
- public toastType: 'info' | 'success' | 'warning' | 'danger',
- public providers: Provider[] = []
- ) {}
-
- public close = () => {
- this.isOpen = false;
- }
-}
diff --git a/src/modules/toast/types/toast-type.ts b/src/modules/toast/types/toast-type.ts
new file mode 100644
index 000000000..590bb6a1a
--- /dev/null
+++ b/src/modules/toast/types/toast-type.ts
@@ -0,0 +1 @@
+export type SkyToastType = 'info' | 'success' | 'warning' | 'danger';
From a4c78e8d44acb3f475941362145fc0dabff13e7c Mon Sep 17 00:00:00 2001
From: Blackbaud-SteveBrush
Date: Thu, 10 May 2018 12:29:59 -0400
Subject: [PATCH 31/46] Unit tests
---
src/demos/toast/toast-demo.component.html | 2 +
src/demos/toast/toast-demo.component.ts | 4 +
src/modules/toast/fixtures/index.ts | 3 +
.../toast/fixtures/toast-body-context.ts | 5 +
.../toast-body.component.fixture.html | 12 +
.../fixtures/toast-body.component.fixture.ts | 18 ++
.../toast/fixtures/toast-fixtures.module.ts | 20 +-
.../fixtures/toaster.component.fixture.ts | 11 +
.../toast/toast-adapter.service.spec.ts | 2 +-
src/modules/toast/toast-adapter.service.ts | 5 +-
src/modules/toast/toast.component.html | 2 +-
src/modules/toast/toast.component.scss | 2 +-
src/modules/toast/toast.component.spec.ts | 170 +++---------
src/modules/toast/toast.module.ts | 4 +-
src/modules/toast/toast.service.spec.ts | 248 +++++++-----------
src/modules/toast/toast.service.ts | 31 ++-
src/modules/toast/toast.spec.ts | 36 +++
src/modules/toast/toaster.component.html | 2 +-
src/modules/toast/toaster.component.spec.ts | 212 ++++++++++++---
19 files changed, 455 insertions(+), 334 deletions(-)
create mode 100644 src/modules/toast/fixtures/toast-body-context.ts
create mode 100644 src/modules/toast/fixtures/toast-body.component.fixture.html
create mode 100644 src/modules/toast/fixtures/toast-body.component.fixture.ts
create mode 100644 src/modules/toast/fixtures/toaster.component.fixture.ts
create mode 100644 src/modules/toast/toast.spec.ts
diff --git a/src/demos/toast/toast-demo.component.html b/src/demos/toast/toast-demo.component.html
index 2826e914a..910abc5c3 100644
--- a/src/demos/toast/toast-demo.component.html
+++ b/src/demos/toast/toast-demo.component.html
@@ -33,4 +33,6 @@
>
Open toast with custom component
+
+ Close all
diff --git a/src/demos/toast/toast-demo.component.ts b/src/demos/toast/toast-demo.component.ts
index 21926aa2d..bc0a966ef 100644
--- a/src/demos/toast/toast-demo.component.ts
+++ b/src/demos/toast/toast-demo.component.ts
@@ -53,4 +53,8 @@ export class SkyToastDemoComponent {
console.log('Custom component toast closed!');
});
}
+
+ public closeAll(): void {
+ this.toastService.closeAll();
+ }
}
diff --git a/src/modules/toast/fixtures/index.ts b/src/modules/toast/fixtures/index.ts
index 009045a31..020851ae3 100644
--- a/src/modules/toast/fixtures/index.ts
+++ b/src/modules/toast/fixtures/index.ts
@@ -1,2 +1,5 @@
+export * from './toast-body-context';
+export * from './toast-body.component.fixture';
export * from './toast-fixtures.module';
export * from './toast.component.fixture';
+export * from './toaster.component.fixture';
diff --git a/src/modules/toast/fixtures/toast-body-context.ts b/src/modules/toast/fixtures/toast-body-context.ts
new file mode 100644
index 000000000..13f40d186
--- /dev/null
+++ b/src/modules/toast/fixtures/toast-body-context.ts
@@ -0,0 +1,5 @@
+export class SkyToastBodyTestContext {
+ constructor(
+ public message: string
+ ) { }
+}
diff --git a/src/modules/toast/fixtures/toast-body.component.fixture.html b/src/modules/toast/fixtures/toast-body.component.fixture.html
new file mode 100644
index 000000000..f1a8009c1
--- /dev/null
+++ b/src/modules/toast/fixtures/toast-body.component.fixture.html
@@ -0,0 +1,12 @@
+
+ {{ context.message }}
+
+
+ Close
+
diff --git a/src/modules/toast/fixtures/toast-body.component.fixture.ts b/src/modules/toast/fixtures/toast-body.component.fixture.ts
new file mode 100644
index 000000000..5964e308d
--- /dev/null
+++ b/src/modules/toast/fixtures/toast-body.component.fixture.ts
@@ -0,0 +1,18 @@
+import { Component } from '@angular/core';
+import { SkyToastInstance } from '../toast-instance';
+import { SkyToastBodyTestContext } from './toast-body-context';
+
+@Component({
+ selector: 'sky-toast-body-test',
+ templateUrl: './toast-body.component.fixture.html'
+})
+export class SkyToastBodyTestComponent {
+ constructor(
+ public context: SkyToastBodyTestContext,
+ private instance: SkyToastInstance
+ ) { }
+
+ public close(): void {
+ this.instance.close();
+ }
+}
diff --git a/src/modules/toast/fixtures/toast-fixtures.module.ts b/src/modules/toast/fixtures/toast-fixtures.module.ts
index 92be37bab..4b65acc1c 100644
--- a/src/modules/toast/fixtures/toast-fixtures.module.ts
+++ b/src/modules/toast/fixtures/toast-fixtures.module.ts
@@ -7,6 +7,10 @@ import {
CommonModule
} from '@angular/common';
+import {
+ NoopAnimationsModule
+} from '@angular/platform-browser/animations';
+
import {
SkyToastModule
} from '../toast.module';
@@ -14,18 +18,30 @@ import {
import {
SkyToastTestComponent
} from './toast.component.fixture';
+
+import {
+ SkyToasterTestComponent
+} from './toaster.component.fixture';
+import { SkyToastBodyTestComponent } from '.';
// #endregion
@NgModule({
declarations: [
- SkyToastTestComponent
+ SkyToastTestComponent,
+ SkyToastBodyTestComponent,
+ SkyToasterTestComponent
],
imports: [
CommonModule,
+ NoopAnimationsModule,
SkyToastModule
],
exports: [
- SkyToastTestComponent
+ SkyToastTestComponent,
+ SkyToasterTestComponent
+ ],
+ entryComponents: [
+ SkyToastBodyTestComponent
]
})
export class SkyToastFixturesModule { }
diff --git a/src/modules/toast/fixtures/toaster.component.fixture.ts b/src/modules/toast/fixtures/toaster.component.fixture.ts
new file mode 100644
index 000000000..f504f6a5d
--- /dev/null
+++ b/src/modules/toast/fixtures/toaster.component.fixture.ts
@@ -0,0 +1,11 @@
+// #region imports
+import {
+ Component
+} from '@angular/core';
+// #endregion
+
+@Component({
+ selector: 'sky-test-cmp',
+ template: 'noop'
+})
+export class SkyToasterTestComponent { }
diff --git a/src/modules/toast/toast-adapter.service.spec.ts b/src/modules/toast/toast-adapter.service.spec.ts
index cc20ca31b..0d3831d81 100644
--- a/src/modules/toast/toast-adapter.service.spec.ts
+++ b/src/modules/toast/toast-adapter.service.spec.ts
@@ -7,7 +7,7 @@ import {
import {
SkyWindowRefService
-} from '../../window';
+} from '../window';
import {
SkyToastAdapterService
diff --git a/src/modules/toast/toast-adapter.service.ts b/src/modules/toast/toast-adapter.service.ts
index 99d6aa68a..667534ec1 100644
--- a/src/modules/toast/toast-adapter.service.ts
+++ b/src/modules/toast/toast-adapter.service.ts
@@ -12,6 +12,7 @@ import {
@Injectable()
export class SkyToastAdapterService {
+ private hostElement: any;
private renderer: Renderer2;
constructor(
@@ -23,12 +24,12 @@ export class SkyToastAdapterService {
public appendToBody(element: any): void {
const body = this.windowRef.getWindow().document.body;
+ this.hostElement = element;
this.renderer.appendChild(body, element);
}
public removeHostElement(): void {
const document = this.windowRef.getWindow().document;
- const hostElement = document.querySelector('sky-toaster');
- this.renderer.removeChild(document.body, hostElement);
+ this.renderer.removeChild(document.body, this.hostElement);
}
}
diff --git a/src/modules/toast/toast.component.html b/src/modules/toast/toast.component.html
index c39b0a5e6..4415e64fc 100644
--- a/src/modules/toast/toast.component.html
+++ b/src/modules/toast/toast.component.html
@@ -13,7 +13,7 @@
{
+describe('Toast component', () => {
let fixture: ComponentFixture;
let component: SkyToastTestComponent;
+ let toastComponent: SkyToastComponent;
beforeEach(() => {
TestBed.configureTestingModule({
@@ -27,148 +33,40 @@ fdescribe('Toast component', () => {
fixture = TestBed.createComponent(SkyToastTestComponent);
component = fixture.componentInstance;
+ toastComponent = component.toastComponent;
});
afterEach(() => {
fixture.destroy();
});
+ function verifyType(type?: SkyToastType) {
+ component.toastType = type;
+ fixture.detectChanges();
+ const toastElement = fixture.nativeElement
+ .querySelector(`.sky-toast-${toastComponent.toastType}`);
+ expect(toastElement).not.toBeNull();
+ }
+
it('should set defaults', () => {
- expect(component.toastComponent.toastType).toEqual('info');
+ expect(toastComponent.toastType).toEqual('info');
});
-});
-
-// import {
-// ComponentFactoryResolver,
-// Injector
-// } from '@angular/core';
-// import {
-// TestBed
-// } from '@angular/core/testing';
-
-// import {
-// SkyToastInstance
-// } from '../types';
-// import {
-// SkyToastComponent
-// } from '.';
-
-// describe('Toast component', () => {
-// class TestComponent { constructor(public instance: SkyToastInstance) {} }
-// let instance: SkyToastInstance;
-
-// beforeEach(() => {
-// TestBed.configureTestingModule({
-// providers: [
-// Injector,
-// {
-// provide: ComponentFactoryResolver,
-// useValue: {
-// resolveComponentFactory() {
-// return {
-// create() {
-// return {
-// destroy() {},
-// hostView: {
-// rootNodes: [
-// {}
-// ]
-// },
-// instance: {
-// messageStream: {
-// take() {
-// return {
-// subscribe() { }
-// };
-// },
-// next() {}
-// },
-// attach() {
-// return {
-// close() { },
-// closed: {
-// take() {
-// return {
-// subscribe() { }
-// };
-// }
-// }
-// };
-// }
-// }
-// };
-// }
-// };
-// }
-// }
-// }
-// ]
-// });
-// });
-
-// it('should instantiate a toast without a custom component',
-// () => {
-// instance = new SkyToastInstance('My message', undefined, 'danger', []);
-// let toast: SkyToastComponent;
-// try {
-// toast = new SkyToastComponent(undefined, undefined);
-// toast.instance = instance;
-
-// toast.ngOnInit();
-// expect((toast as any).customComponent).toBeFalsy();
-
-// toast.ngOnDestroy();
-// expect((toast as any).customComponent).toBeFalsy();
-// } catch (error) {
-// fail();
-// }
-// expect(toast).toBeTruthy();
-// });
-// it('should show proper closed or open states', () => {
-// instance = new SkyToastInstance('My message', undefined, 'danger', []);
-// let toast: SkyToastComponent = new SkyToastComponent(undefined, undefined);
-// toast.instance = instance;
-
-// toast.ngOnInit();
-
-// expect(toast.getAnimationState()).toBe('toastOpen');
-// instance.close();
-// toast.animationDone(undefined);
-// expect(toast.getAnimationState()).toBe('toastClosed');
-// expect(toast).toBeTruthy();
-// });
-
-// it('should instantiate a toast with a custom component and tear it down',
-// () => {
-// let clearCalled: boolean = false;
-// let createComponentCalled: boolean = false;
-// let destroyCalled: boolean = false;
-
-// instance = new SkyToastInstance(undefined, TestComponent, 'danger', []);
-
-// let toast: SkyToastComponent;
-// toast = new SkyToastComponent(TestBed.get(ComponentFactoryResolver), TestBed.get(Injector));
-// toast.instance = instance;
-
-// (toast as any).toastHost = {
-// clear: () => { clearCalled = true; },
-// createComponent: () => {
-// createComponentCalled = true;
-// return {
-// instance: {},
-// destroy: () => { destroyCalled = true; }
-// };
-// }
-// };
-
-// toast.ngOnInit();
-// expect((toast as any).customComponent).toBeTruthy();
-// expect(clearCalled).toBeTruthy();
-// expect(createComponentCalled).toBeTruthy();
+ it('should allow setting the toast type', () => {
+ verifyType(); // default
+ verifyType('info');
+ verifyType('success');
+ verifyType('warning');
+ verifyType('danger');
+ });
-// toast.ngOnDestroy();
-// expect(destroyCalled).toBeTruthy();
-// expect(toast).toBeTruthy();
-// });
-// });
+ it('should close the toast when clicking close button', () => {
+ fixture.detectChanges();
+ expect(toastComponent['isOpen']).toEqual(true);
+ expect(toastComponent.getAnimationState()).toEqual('toastOpen');
+ fixture.nativeElement.querySelector('.sky-toast-btn-close').click();
+ fixture.detectChanges();
+ expect(toastComponent['isOpen']).toEqual(false);
+ expect(toastComponent.getAnimationState()).toEqual('toastClosed');
+ });
+});
diff --git a/src/modules/toast/toast.module.ts b/src/modules/toast/toast.module.ts
index 44127183c..ca0a980c8 100644
--- a/src/modules/toast/toast.module.ts
+++ b/src/modules/toast/toast.module.ts
@@ -30,6 +30,7 @@ import {
import {
SkyToastService
} from './toast.service';
+import { SkyWindowRefService } from '../window';
// #endregion
@NgModule({
@@ -47,7 +48,8 @@ import {
],
providers: [
SkyToastService,
- SkyToastAdapterService
+ SkyToastAdapterService,
+ SkyWindowRefService
],
entryComponents: [
SkyToastBodyComponent,
diff --git a/src/modules/toast/toast.service.spec.ts b/src/modules/toast/toast.service.spec.ts
index 28d01e0ae..24368d86a 100644
--- a/src/modules/toast/toast.service.spec.ts
+++ b/src/modules/toast/toast.service.spec.ts
@@ -1,34 +1,32 @@
-import {
- ApplicationRef,
- ComponentFactoryResolver,
- Injector
-} from '@angular/core';
+// #region imports
import {
TestBed
} from '@angular/core/testing';
-import {
- SkyWindowRefService
-} from '../window';
+import 'rxjs/add/operator/take';
import {
- SkyToastService
-} from './toast.service';
-import {
- SkyToastAdapterService
-} from './toast-adapter.service';
-import {
- SkyToastInstance
-} from './toast-instance';
+ expect
+} from '@blackbaud/skyux-builder/runtime/testing/browser';
+
+import { SkyToastFixturesModule } from './fixtures';
+
+import { SkyToastService } from './toast.service';
+import { ApplicationRef } from '@angular/core';
+import { SkyToastAdapterService } from './toast-adapter.service';
+import { SkyToastInstance } from '.';
+import { SkyToast } from './toast';
+// #endregion
describe('Toast service', () => {
- class TestComponent { constructor(public message: SkyToastInstance) { } }
let toastService: SkyToastService;
beforeEach(() => {
TestBed.configureTestingModule({
+ imports: [
+ SkyToastFixturesModule
+ ],
providers: [
- SkyToastService,
{
provide: SkyToastAdapterService,
useValue: {
@@ -42,175 +40,131 @@ describe('Toast service', () => {
attachView() {},
detachView() {}
}
- },
- Injector,
- {
- provide: ComponentFactoryResolver,
- useValue: {
- resolveComponentFactory() {
- return {
- create() {
- return {
- destroy() {},
- hostView: {
- rootNodes: [
- {}
- ]
- },
- instance: {
- messageStream: {
- take() {
- return {
- subscribe() { }
- };
- },
- next() {}
- },
- attach() {
- return {
- close() { },
- closed: {
- take() {
- return {
- subscribe() { }
- };
- }
- }
- };
- }
- }
- };
- }
- };
- }
- }
- },
- SkyWindowRefService
+ }
]
});
+
toastService = TestBed.get(SkyToastService);
});
it('should only create a single host component', () => {
- const spy = spyOn(toastService as any, 'createHostComponent').and.callThrough();
- toastService.openMessage('message');
- toastService.openMessage('message');
- expect(spy.calls.count()).toEqual(1);
+ const spy = spyOn(toastService as any, 'createHostComponent').and.callThrough();
+ toastService.openMessage('message');
+ toastService.openMessage('message');
+ expect(spy.calls.count()).toEqual(1);
});
it('should return an instance with a close method', () => {
- const toast = toastService.openMessage('message');
- expect(typeof toast.close).toEqual('function');
+ const toast = toastService.openMessage('message');
+ expect(typeof toast.close).toEqual('function');
+ });
+
+ it('should only remove the host element if it exists', () => {
+ toastService.openMessage('message');
+ const spy = spyOn(toastService['host'], 'destroy').and.callThrough();
+ toastService['removeHostComponent']();
+ toastService['removeHostComponent']();
+ expect(spy.calls.count()).toEqual(1);
});
it('should expose a method to remove the toast from the DOM', () => {
- let message: SkyToastInstance = toastService.openMessage('message');
- const spy = spyOn(message, 'close').and.callThrough();
- toastService.ngOnDestroy();
- expect(spy).toHaveBeenCalledWith();
+ const instance: SkyToastInstance = toastService.openMessage('message');
+ const spy = spyOn(instance, 'close').and.callThrough();
+ toastService.ngOnDestroy();
+ expect(spy).toHaveBeenCalledWith();
});
describe('openMessage() method', () => {
it('should open a toast with the given message and configuration', function() {
- let internalMessage: SkyToastInstance = toastService.openMessage('Real message', {toastType: 'danger'});
+ const instance = toastService.openMessage('Real message', {
+ type: 'danger'
+ });
- expect(internalMessage).toBeTruthy();
- expect(internalMessage.message).toBe('Real message');
- expect(internalMessage.toastType).toBe('danger');
-
- expect(internalMessage.close).toBeTruthy();
+ expect(instance).toBeTruthy();
+ expect(instance.close).toBeTruthy();
let isClosedCalled = false;
- internalMessage.isClosed.subscribe(() => isClosedCalled = true);
+ instance.closed.subscribe(() => isClosedCalled = true);
- expect(internalMessage.isOpen).toBeTruthy();
- expect(isClosedCalled).toBeFalsy();
+ expect(isClosedCalled).toEqual(false);
+ instance.close();
+ expect(isClosedCalled).toEqual(true);
});
- it('should remove message from queue when the message is closed', function(done: Function) {
- let internalMessage: SkyToastInstance = toastService.openMessage('My message', {toastType: 'danger'});
+ it('should remove message from queue when the message is closed', () => {
+ const instance = toastService.openMessage('My message');
let isClosedCalled = false;
- internalMessage.isClosed.subscribe(() => isClosedCalled = true);
-
- internalMessage.close();
- internalMessage.isClosed.next();
- setTimeout(() => {
- toastService.toastInstances.subscribe((value) => {
- if (!internalMessage.isOpen) {
- expect(value.length).toBe(0);
- }
- });
- expect(internalMessage.isOpen).toBeFalsy();
- expect(isClosedCalled).toBeTruthy();
- done();
- }, 600);
- });
+ instance.closed.subscribe(() => isClosedCalled = true);
- it('should not error when closing an already closed message', function(done: Function) {
- let internalMessage: SkyToastInstance = toastService.openMessage('My message', {toastType: 'danger'});
- internalMessage.close();
- setTimeout(() => {
- try {
- internalMessage.close();
- } catch (error) {
- fail();
- }
- done();
- }, 600);
- });
+ instance.close();
- it('should open specific toast types', function () {
- let infoMessage = toastService.openMessage('info message', {toastType: 'info'});
- let warningMessage = toastService.openMessage('warning message', {toastType: 'warning'});
- let dangerMessage = toastService.openMessage('danger message', {toastType: 'danger'});
- let successMessage = toastService.openMessage('success message', {toastType: 'success'});
-
- expect(infoMessage.toastType).toBe('info');
- expect(warningMessage.toastType).toBe('warning');
- expect(dangerMessage.toastType).toBe('danger');
- expect(successMessage.toastType).toBe('success');
+ toastService.toastStream.take(1).subscribe((value) => {
+ expect(value.length).toEqual(0);
+ expect(isClosedCalled).toBeTruthy();
+ });
});
- it('should open info toast type when no or an unknown type is supplied', function () {
- let emptyType: SkyToastInstance = toastService.openMessage('info message');
- expect(emptyType.toastType).toBe('info');
+ it('should complete the instance closed emitter', () => {
+ const instance = toastService.openMessage('My message');
+ let numTimesCalled = 0;
+ instance.closed.subscribe(() => {
+ numTimesCalled++;
+ });
+ instance.close();
+ instance.close();
+ instance.close();
+ expect(numTimesCalled).toEqual(1);
});
});
- describe('openTemplatedMessage() method', () => {
- it('should open a custom toast with the given component type and configuration', function() {
- let internalMessage: SkyToastInstance = toastService.openTemplatedMessage(TestComponent, {toastType: 'danger'});
+ describe('openComponent() method', () => {
+ class TestContext {
+ public message: string;
+ }
- expect(internalMessage).toBeTruthy();
- expect(internalMessage.message).toBeFalsy();
- expect(internalMessage.customComponentType).toBeTruthy();
- expect(internalMessage.toastType).toBe('danger');
+ class TestComponent { }
- expect(internalMessage.close).toBeTruthy();
+ it('should open a custom toast with the given component type and configuration', () => {
+ const context = new TestContext();
+ context.message = 'Hello!';
- let isClosedCalled = false;
- internalMessage.isClosed.subscribe(() => isClosedCalled = true);
+ const providers = {
+ provide: TestContext,
+ useValue: context
+ };
- expect(internalMessage.isOpen).toBeTruthy();
- expect(isClosedCalled).toBeFalsy();
- });
-
- it('should open a custom toast with the given component type and configuration', function() {
- let internalMessage: SkyToastInstance = toastService.openTemplatedMessage(TestComponent, {toastType: 'danger'});
+ const instance = toastService.openComponent(
+ TestComponent,
+ {
+ type: 'danger'
+ },
+ [providers]
+ );
- expect(internalMessage).toBeTruthy();
- expect(internalMessage.message).toBeFalsy();
- expect(internalMessage.customComponentType).toBeTruthy();
- expect(internalMessage.toastType).toBe('danger');
+ toastService.toastStream.take(1).subscribe((toasts: SkyToast[]) => {
+ expect(toasts[0].bodyComponentProviders[0]).toEqual(providers);
+ });
- expect(internalMessage.close).toBeTruthy();
+ expect(instance).toBeTruthy();
+ expect(instance.close).toBeTruthy();
let isClosedCalled = false;
- internalMessage.isClosed.subscribe(() => isClosedCalled = true);
+ instance.closed.subscribe(() => isClosedCalled = true);
+
+ expect(isClosedCalled).toEqual(false);
+ instance.close();
+ expect(isClosedCalled).toEqual(true);
+ });
+
+ it('should handle empty providers', () => {
+ toastService.openComponent(TestComponent, {
+ type: 'danger'
+ });
- expect(internalMessage.isOpen).toBeTruthy();
- expect(isClosedCalled).toBeFalsy();
+ toastService.toastStream.take(1).subscribe((toasts: SkyToast[]) => {
+ expect(toasts[0].bodyComponentProviders.length).toEqual(1);
+ });
});
});
});
diff --git a/src/modules/toast/toast.service.ts b/src/modules/toast/toast.service.ts
index 1edee5132..8888b2f3a 100644
--- a/src/modules/toast/toast.service.ts
+++ b/src/modules/toast/toast.service.ts
@@ -66,12 +66,9 @@ export class SkyToastService implements OnDestroy {
) { }
public ngOnDestroy() {
- this.host = undefined;
- this.toasts.forEach(toast => toast.instance.close());
- this.toasts = [];
- this._toastStream.next(this.toasts);
+ this.closeAll();
+ this.removeHostComponent();
this._toastStream.complete();
- this.adapter.removeHostElement();
}
public openMessage(
@@ -94,10 +91,6 @@ export class SkyToastService implements OnDestroy {
config?: SkyToastConfig,
providers: Provider[] = []
): SkyToastInstance {
- if (!this.host) {
- this.host = this.createHostComponent();
- }
-
const instance = new SkyToastInstance();
providers.push({
@@ -112,7 +105,17 @@ export class SkyToastService implements OnDestroy {
return instance;
}
+ public closeAll(): void {
+ this.toasts.forEach(toast => toast.instance.close());
+ this.toasts = [];
+ this._toastStream.next(this.toasts);
+ }
+
private addToast(toast: SkyToast): void {
+ if (!this.host) {
+ this.host = this.createHostComponent();
+ }
+
this.toasts.push(toast);
this._toastStream.next(this.toasts);
toast.instance.closed.subscribe(() => {
@@ -137,4 +140,14 @@ export class SkyToastService implements OnDestroy {
return componentRef;
}
+
+ private removeHostComponent() {
+ if (this.host) {
+ this.appRef.detachView(this.host.hostView);
+ this.host.destroy();
+ this.host = undefined;
+ }
+
+ this.adapter.removeHostElement();
+ }
}
diff --git a/src/modules/toast/toast.spec.ts b/src/modules/toast/toast.spec.ts
new file mode 100644
index 000000000..7868192a7
--- /dev/null
+++ b/src/modules/toast/toast.spec.ts
@@ -0,0 +1,36 @@
+// #region imports
+import {
+ expect
+} from '@blackbaud/skyux-builder/runtime/testing/browser';
+
+import {Provider } from '@angular/core';
+import { SkyToast } from './toast';
+import { SkyToastConfig, SkyToastInstance } from '.';
+// #endregion
+
+describe('Toast class', () => {
+ it('should set defaults', () => {
+ const component = function () {};
+ const providers: Provider[] = [];
+ const config: SkyToastConfig = {};
+ const toast = new SkyToast(component, providers, config);
+ expect(toast.bodyComponent).toEqual(component);
+ expect(toast.bodyComponentProviders).toEqual(providers);
+ expect(toast.config).toEqual(config);
+ expect(toast.instance).toBeUndefined();
+ });
+
+ it('should only allow setting the instance once', () => {
+ const component = function () {};
+ const providers: Provider[] = [];
+ const config: SkyToastConfig = {};
+ const toast = new SkyToast(component, providers, config);
+ const firstInstance = new SkyToastInstance();
+ (firstInstance as any).fooName = 'first';
+ const secondInstance = new SkyToastInstance();
+ (secondInstance as any).fooName = 'second';
+ toast.instance = firstInstance;
+ toast.instance = secondInstance;
+ expect((toast.instance as any).fooName).toEqual('first');
+ });
+});
diff --git a/src/modules/toast/toaster.component.html b/src/modules/toast/toaster.component.html
index 86b3a5e85..71f61e7c8 100644
--- a/src/modules/toast/toaster.component.html
+++ b/src/modules/toast/toaster.component.html
@@ -3,7 +3,7 @@
>
{
+describe('Toast component', () => {
+ let fixture: ComponentFixture;
let toastService: SkyToastService;
- let toastInstances: BehaviorSubject = new BehaviorSubject([]);
+ let applicationRef: ApplicationRef;
beforeEach(() => {
TestBed.configureTestingModule({
- providers: [
- {
- provide: SkyToastService,
- useValue: {
- toastInstances: toastInstances
- }
- }
- ]});
- toastService = TestBed.get(SkyToastService);
- });
+ imports: [
+ SkyToastFixturesModule
+ ]
+ });
- it('should instantiate a toaster with its own subscription to the toastInstance list',
- (done: Function) => {
- let instance: SkyToastInstance = new SkyToastInstance('My message', undefined, 'danger', []);
- instance.isClosed.subscribe(() => {
- toastInstances.next([]);
- });
- toastInstances.next([instance]);
-
- let container: SkyToasterComponent = new SkyToasterComponent(toastService);
- container.ngOnInit();
- container.toastInstances.subscribe((value) => {
- expect(value[0]).toBe(instance);
- done();
- });
+ fixture = TestBed.createComponent(SkyToasterTestComponent);
});
+
+ beforeEach(inject(
+ [ApplicationRef, SkyToastService],
+ (
+ _applicationRef: ApplicationRef,
+ _toastService: SkyToastService
+ ) => {
+ applicationRef = _applicationRef;
+ toastService = _toastService;
+ }
+ ));
+
+ afterEach(fakeAsync(() => {
+ toastService.closeAll();
+ applicationRef.tick();
+ tick();
+ fixture.detectChanges();
+ fixture.destroy();
+ }));
+
+ function getToastElements(): NodeListOf {
+ return document.querySelectorAll('sky-toast');
+ }
+
+ function openMessage(message = ''): SkyToastInstance {
+ const instance = toastService.openMessage(message);
+ fixture.detectChanges();
+ tick();
+ return instance;
+ }
+
+ function openComponent(message = ''): SkyToastInstance {
+ const providers = [{
+ provide: SkyToastBodyTestContext,
+ useValue: new SkyToastBodyTestContext(message)
+ }];
+ const instance = toastService.openComponent(SkyToastBodyTestComponent, {}, providers);
+ fixture.detectChanges();
+ tick();
+ return instance;
+ }
+
+ it('should not create a toaster element if one exists', fakeAsync(() => {
+ openMessage();
+
+ let toasters = document.querySelectorAll('sky-toaster');
+ expect(toasters.length).toEqual(1);
+
+ openMessage();
+ toasters = document.querySelectorAll('sky-toaster');
+ expect(toasters.length).toEqual(1);
+
+ const toasts = getToastElements();
+ expect(toasts.length).toEqual(2);
+ }));
+
+ it('should display a toast component with defaults', fakeAsync(() => {
+ const message = 'Hello, World!';
+ openMessage(message);
+
+ const toasts = getToastElements();
+ expect(toasts.length).toEqual(1);
+ expect(toasts.item(0).querySelector('.sky-toast-content')).toHaveText(message, true);
+ expect(toasts.item(0).querySelector('.sky-toast-info')).toExist();
+ }));
+
+ it('should handle closing a toast', fakeAsync(() => {
+ openMessage();
+ openMessage();
+ openMessage();
+
+ let toasts = getToastElements();
+ expect(toasts.length).toEqual(3);
+
+ toasts.item(0).querySelector('.sky-toast-btn-close').click();
+ fixture.detectChanges();
+ tick();
+
+ toasts = getToastElements();
+ expect(toasts.length).toEqual(2);
+ }));
+
+ it('should handle closing a toast instance from inside a custom component', fakeAsync(() => {
+ const message = 'Hello, component!';
+ openComponent(message);
+
+ let toasts = getToastElements();
+ expect(toasts.length).toEqual(1);
+ expect(toasts.item(0).querySelector('.sky-toast-body-test-content')).toHaveText(message, true);
+
+ toasts.item(0).querySelector('.sky-toast-body-test-btn-close').click();
+ fixture.detectChanges();
+ tick();
+
+ toasts = getToastElements();
+ expect(toasts.length).toEqual(0);
+ }));
});
+
+// import {
+// TestBed
+// } from '@angular/core/testing';
+
+// import {
+// BehaviorSubject
+// } from 'rxjs';
+
+// import {
+// SkyToastService,
+// SkyToasterComponent
+// } from '.';
+// import {
+// SkyToastInstance
+// } from './types';
+
+// describe('Toaster component', () => {
+// let toastService: SkyToastService;
+// let toastInstances: BehaviorSubject = new BehaviorSubject([]);
+
+// beforeEach(() => {
+// TestBed.configureTestingModule({
+// providers: [
+// {
+// provide: SkyToastService,
+// useValue: {
+// toastInstances: toastInstances
+// }
+// }
+// ]});
+// toastService = TestBed.get(SkyToastService);
+// });
+
+// it('should instantiate a toaster with its own subscription to the toastInstance list',
+// (done: Function) => {
+// let instance: SkyToastInstance = new SkyToastInstance('My message', undefined, 'danger', []);
+// instance.isClosed.subscribe(() => {
+// toastInstances.next([]);
+// });
+// toastInstances.next([instance]);
+
+// let container: SkyToasterComponent = new SkyToasterComponent(toastService);
+// container.ngOnInit();
+// container.toastInstances.subscribe((value) => {
+// expect(value[0]).toBe(instance);
+// done();
+// });
+// });
+// });
From c6d9b968dedae6c77d13503d9a59237ff9c6fb9e Mon Sep 17 00:00:00 2001
From: Blackbaud-SteveBrush
Date: Thu, 10 May 2018 13:10:16 -0400
Subject: [PATCH 32/46] General cleanup
---
.../src/app/toast/index.html | 3 +-
.../src/app/toast/toast-demo.component.html | 6 +-
.../src/app/toast/toast-demo.component.ts | 8 +--
.../src/app/toast/toast-visual.component.html | 2 +-
.../src/app/toast/toast-visual.component.ts | 4 +-
.../src/app/toast/toast.visual-spec.ts | 22 ++------
src/demos/demo.service.ts | 10 +++-
src/demos/toast/index.ts | 2 +-
src/demos/toast/toast-custom-demo-context.ts | 5 ++
.../toast/toast-custom-demo.component.html | 6 +-
.../toast/toast-custom-demo.component.ts | 6 +-
src/demos/toast/toast-demo.component.html | 8 ++-
src/demos/toast/toast-demo.component.ts | 14 ++++-
.../fixtures/toast-body.component.fixture.ts | 16 +++++-
.../toast/fixtures/toast-fixtures.module.ts | 5 +-
.../fixtures/toast.component.fixture.html | 3 +-
.../toast/fixtures/toast.component.fixture.ts | 2 +-
.../toast/toast-adapter.service.spec.ts | 3 +
src/modules/toast/toast-instance.ts | 1 +
src/modules/toast/toast.component.spec.ts | 5 +-
src/modules/toast/toast.module.ts | 5 +-
src/modules/toast/toast.service.spec.ts | 28 ++++++++--
src/modules/toast/toast.service.ts | 11 ++--
src/modules/toast/toast.spec.ts | 18 +++++-
src/modules/toast/toaster.component.scss | 2 +-
src/modules/toast/toaster.component.spec.ts | 56 ++-----------------
26 files changed, 143 insertions(+), 108 deletions(-)
create mode 100644 src/demos/toast/toast-custom-demo-context.ts
diff --git a/skyux-spa-visual-tests/src/app/toast/index.html b/skyux-spa-visual-tests/src/app/toast/index.html
index b1db80f7e..0d8cb162a 100644
--- a/skyux-spa-visual-tests/src/app/toast/index.html
+++ b/skyux-spa-visual-tests/src/app/toast/index.html
@@ -1 +1,2 @@
-
+
+
diff --git a/skyux-spa-visual-tests/src/app/toast/toast-demo.component.html b/skyux-spa-visual-tests/src/app/toast/toast-demo.component.html
index cd3a16d25..1f95fb1b3 100644
--- a/skyux-spa-visual-tests/src/app/toast/toast-demo.component.html
+++ b/skyux-spa-visual-tests/src/app/toast/toast-demo.component.html
@@ -1 +1,5 @@
-Toast component
+
+ Toast component
+
diff --git a/skyux-spa-visual-tests/src/app/toast/toast-demo.component.ts b/skyux-spa-visual-tests/src/app/toast/toast-demo.component.ts
index 1bc1611f9..d965e4fbf 100644
--- a/skyux-spa-visual-tests/src/app/toast/toast-demo.component.ts
+++ b/skyux-spa-visual-tests/src/app/toast/toast-demo.component.ts
@@ -1,21 +1,19 @@
import {
- Component,
- ChangeDetectionStrategy
+ ChangeDetectionStrategy,
+ Component
} from '@angular/core';
import {
- SkyToastService,
SkyToastInstance
} from '@blackbaud/skyux/dist/core';
@Component({
selector: 'sky-test-cmp-toast',
templateUrl: './toast-demo.component.html',
- providers: [SkyToastService],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ToastDemoComponent {
constructor(
public message: SkyToastInstance
- ) {}
+ ) { }
}
diff --git a/skyux-spa-visual-tests/src/app/toast/toast-visual.component.html b/skyux-spa-visual-tests/src/app/toast/toast-visual.component.html
index a6c5337d0..093bec4b2 100644
--- a/skyux-spa-visual-tests/src/app/toast/toast-visual.component.html
+++ b/skyux-spa-visual-tests/src/app/toast/toast-visual.component.html
@@ -10,7 +10,7 @@
+ (click)="openComponent()">
Open custom toast
diff --git a/skyux-spa-visual-tests/src/app/toast/toast-visual.component.ts b/skyux-spa-visual-tests/src/app/toast/toast-visual.component.ts
index 4b11c40ee..05e4f7a79 100644
--- a/skyux-spa-visual-tests/src/app/toast/toast-visual.component.ts
+++ b/skyux-spa-visual-tests/src/app/toast/toast-visual.component.ts
@@ -25,7 +25,7 @@ export class ToastVisualComponent {
this.toastService.openMessage('Toast message');
}
- public openTemplatedToast() {
- this.toastService.openTemplatedMessage(ToastDemoComponent, {});
+ public openComponent() {
+ this.toastService.openComponent(ToastDemoComponent, {});
}
}
diff --git a/skyux-spa-visual-tests/src/app/toast/toast.visual-spec.ts b/skyux-spa-visual-tests/src/app/toast/toast.visual-spec.ts
index df221c0d2..8d66e651e 100644
--- a/skyux-spa-visual-tests/src/app/toast/toast.visual-spec.ts
+++ b/skyux-spa-visual-tests/src/app/toast/toast.visual-spec.ts
@@ -3,8 +3,8 @@ import {
} from '../../../config/utils/visual-test-commands';
import {
- element,
- by
+ by,
+ element
} from 'protractor';
describe('Toast', () => {
@@ -16,9 +16,6 @@ describe('Toast', () => {
return SkyVisualTest.compareScreenshot({
screenshotName: 'toast',
selector: 'body'
- }).then(() => {
- expect(by.css('.sky-toast')).toBeTruthy();
- element(by.css('.sky-toast')).click();
});
});
});
@@ -29,11 +26,8 @@ describe('Toast', () => {
element(by.css('.sky-btn-secondary')).click();
SkyVisualTest.moveCursorOffScreen();
return SkyVisualTest.compareScreenshot({
- screenshotName: 'toast',
+ screenshotName: 'toast-component',
selector: 'body'
- }).then(() => {
- expect(by.css('.sky-custom-toast')).toBeTruthy();
- element(by.css('.sky-toast')).click();
});
});
});
@@ -44,11 +38,8 @@ describe('Toast', () => {
element(by.css('.sky-btn-primary')).click();
SkyVisualTest.moveCursorOffScreen();
return SkyVisualTest.compareScreenshot({
- screenshotName: 'toast',
+ screenshotName: 'toast-xs',
selector: 'body'
- }).then(() => {
- expect(by.css('.sky-toast')).toBeTruthy();
- element(by.css('.sky-toast')).click();
});
});
});
@@ -59,11 +50,8 @@ describe('Toast', () => {
element(by.css('.sky-btn-secondary')).click();
SkyVisualTest.moveCursorOffScreen();
return SkyVisualTest.compareScreenshot({
- screenshotName: 'toast',
+ screenshotName: 'toast-component-xs',
selector: 'body'
- }).then(() => {
- expect(by.css('.sky-custom-toast')).toBeTruthy();
- element(by.css('.sky-toast')).click();
});
});
});
diff --git a/src/demos/demo.service.ts b/src/demos/demo.service.ts
index f7e9f6bb5..e9bfcaed8 100644
--- a/src/demos/demo.service.ts
+++ b/src/demos/demo.service.ts
@@ -1,4 +1,7 @@
-import { Injectable } from '@angular/core';
+// #region imports
+import {
+ Injectable
+} from '@angular/core';
import {
SkyActionButtonDemoComponent,
@@ -60,6 +63,7 @@ import {
SkyWaitDemoComponent,
SkyWizardDemoComponent
} from './index';
+// #endregion
/**
* This service provides consumers with the raw file contents for each component demo.
@@ -1041,6 +1045,10 @@ export class SkyDemoService {
fileContents: require('!!raw-loader!./toast/toast-custom-demo.component.ts'),
componentName: 'SkyToastCustomDemoComponent',
bootstrapSelector: 'sky-toast-custom-demo'
+ },
+ {
+ name: 'toast-custom-demo-context.ts',
+ fileContents: require('!!raw-loader!./toast/toast-custom-demo-context.ts')
}
]
},
diff --git a/src/demos/toast/index.ts b/src/demos/toast/index.ts
index 44701c6c8..f10cf3cf3 100644
--- a/src/demos/toast/index.ts
+++ b/src/demos/toast/index.ts
@@ -1,2 +1,2 @@
-export * from './toast-demo.component';
export * from './toast-custom-demo.component';
+export * from './toast-demo.component';
diff --git a/src/demos/toast/toast-custom-demo-context.ts b/src/demos/toast/toast-custom-demo-context.ts
new file mode 100644
index 000000000..7cd2d2e9c
--- /dev/null
+++ b/src/demos/toast/toast-custom-demo-context.ts
@@ -0,0 +1,5 @@
+export class SkyToastCustomDemoContext {
+ constructor(
+ public message: string
+ ) { }
+}
diff --git a/src/demos/toast/toast-custom-demo.component.html b/src/demos/toast/toast-custom-demo.component.html
index 6909a9963..b00248df2 100644
--- a/src/demos/toast/toast-custom-demo.component.html
+++ b/src/demos/toast/toast-custom-demo.component.html
@@ -1,6 +1,8 @@
- This toast has embedded a custom component for its content. It can even link you to
- example.com
+ {{ context.message }}
+
+
+ Some link: example.com
- Close all
+
+ Close all
+
diff --git a/src/demos/toast/toast-demo.component.ts b/src/demos/toast/toast-demo.component.ts
index bc0a966ef..aa7e37c3d 100644
--- a/src/demos/toast/toast-demo.component.ts
+++ b/src/demos/toast/toast-demo.component.ts
@@ -7,6 +7,10 @@ import {
SkyToastType
} from '../../core';
+import {
+ SkyToastCustomDemoContext
+} from './toast-custom-demo-context';
+
import {
SkyToastCustomDemoComponent
} from './toast-custom-demo.component';
@@ -42,11 +46,19 @@ export class SkyToastDemoComponent {
}
public openComponent(): void {
+ const context = new SkyToastCustomDemoContext(
+ 'This toast has embedded a custom component for its content.'
+ );
+
const instance = this.toastService.openComponent(
SkyToastCustomDemoComponent,
{
type: this.selectedType
- }
+ },
+ [{
+ provide: SkyToastCustomDemoContext,
+ useValue: context
+ }]
);
instance.closed.subscribe(() => {
diff --git a/src/modules/toast/fixtures/toast-body.component.fixture.ts b/src/modules/toast/fixtures/toast-body.component.fixture.ts
index 5964e308d..29ec0d7cb 100644
--- a/src/modules/toast/fixtures/toast-body.component.fixture.ts
+++ b/src/modules/toast/fixtures/toast-body.component.fixture.ts
@@ -1,6 +1,16 @@
-import { Component } from '@angular/core';
-import { SkyToastInstance } from '../toast-instance';
-import { SkyToastBodyTestContext } from './toast-body-context';
+// #region imports
+import {
+ Component
+} from '@angular/core';
+
+import {
+ SkyToastInstance
+} from '../toast-instance';
+
+import {
+ SkyToastBodyTestContext
+} from './toast-body-context';
+// #endregion
@Component({
selector: 'sky-toast-body-test',
diff --git a/src/modules/toast/fixtures/toast-fixtures.module.ts b/src/modules/toast/fixtures/toast-fixtures.module.ts
index 4b65acc1c..9ef1222c7 100644
--- a/src/modules/toast/fixtures/toast-fixtures.module.ts
+++ b/src/modules/toast/fixtures/toast-fixtures.module.ts
@@ -15,6 +15,10 @@ import {
SkyToastModule
} from '../toast.module';
+import {
+ SkyToastBodyTestComponent
+} from './toast-body.component.fixture';
+
import {
SkyToastTestComponent
} from './toast.component.fixture';
@@ -22,7 +26,6 @@ import {
import {
SkyToasterTestComponent
} from './toaster.component.fixture';
-import { SkyToastBodyTestComponent } from '.';
// #endregion
@NgModule({
diff --git a/src/modules/toast/fixtures/toast.component.fixture.html b/src/modules/toast/fixtures/toast.component.fixture.html
index d0ccffc10..c0ea0ae60 100644
--- a/src/modules/toast/fixtures/toast.component.fixture.html
+++ b/src/modules/toast/fixtures/toast.component.fixture.html
@@ -1,5 +1,6 @@
+ (closed)="onClosed()"
+>
Inner content here...
diff --git a/src/modules/toast/fixtures/toast.component.fixture.ts b/src/modules/toast/fixtures/toast.component.fixture.ts
index 221f5b726..0b1f0fdb6 100644
--- a/src/modules/toast/fixtures/toast.component.fixture.ts
+++ b/src/modules/toast/fixtures/toast.component.fixture.ts
@@ -23,5 +23,5 @@ export class SkyToastTestComponent {
@ViewChild(SkyToastComponent)
public toastComponent: SkyToastComponent;
- public onClosed(): void {}
+ public onClosed(): void { }
}
diff --git a/src/modules/toast/toast-adapter.service.spec.ts b/src/modules/toast/toast-adapter.service.spec.ts
index 0d3831d81..51991ddef 100644
--- a/src/modules/toast/toast-adapter.service.spec.ts
+++ b/src/modules/toast/toast-adapter.service.spec.ts
@@ -1,6 +1,8 @@
+// #region imports
import {
RendererFactory2
} from '@angular/core';
+
import {
TestBed
} from '@angular/core/testing';
@@ -12,6 +14,7 @@ import {
import {
SkyToastAdapterService
} from './toast-adapter.service';
+// #endregion
describe('Toast adapter service', () => {
diff --git a/src/modules/toast/toast-instance.ts b/src/modules/toast/toast-instance.ts
index 6976e6134..0d0202864 100644
--- a/src/modules/toast/toast-instance.ts
+++ b/src/modules/toast/toast-instance.ts
@@ -15,6 +15,7 @@ export class SkyToastInstance {
private _closed = new EventEmitter();
+ // TODO: Jsdoc here!
public close() {
this._closed.emit();
this._closed.complete();
diff --git a/src/modules/toast/toast.component.spec.ts b/src/modules/toast/toast.component.spec.ts
index 6e0d5c661..e18da1a44 100644
--- a/src/modules/toast/toast.component.spec.ts
+++ b/src/modules/toast/toast.component.spec.ts
@@ -13,10 +13,13 @@ import {
SkyToastTestComponent
} from './fixtures';
+import {
+ SkyToastType
+} from './types';
+
import {
SkyToastComponent
} from './toast.component';
-import { SkyToastType } from '.';
// #endregion
describe('Toast component', () => {
diff --git a/src/modules/toast/toast.module.ts b/src/modules/toast/toast.module.ts
index ca0a980c8..17ee64cc9 100644
--- a/src/modules/toast/toast.module.ts
+++ b/src/modules/toast/toast.module.ts
@@ -11,6 +11,10 @@ import {
SkyResourcesModule
} from '../resources';
+import {
+ SkyWindowRefService
+} from '../window';
+
import {
SkyToastAdapterService
} from './toast-adapter.service';
@@ -30,7 +34,6 @@ import {
import {
SkyToastService
} from './toast.service';
-import { SkyWindowRefService } from '../window';
// #endregion
@NgModule({
diff --git a/src/modules/toast/toast.service.spec.ts b/src/modules/toast/toast.service.spec.ts
index 24368d86a..eae0c5a69 100644
--- a/src/modules/toast/toast.service.spec.ts
+++ b/src/modules/toast/toast.service.spec.ts
@@ -1,4 +1,8 @@
// #region imports
+import {
+ ApplicationRef
+} from '@angular/core';
+
import {
TestBed
} from '@angular/core/testing';
@@ -9,13 +13,25 @@ import {
expect
} from '@blackbaud/skyux-builder/runtime/testing/browser';
-import { SkyToastFixturesModule } from './fixtures';
+import {
+ SkyToastFixturesModule
+} from './fixtures';
+
+import {
+ SkyToast
+} from './toast';
+
+import {
+ SkyToastAdapterService
+} from './toast-adapter.service';
-import { SkyToastService } from './toast.service';
-import { ApplicationRef } from '@angular/core';
-import { SkyToastAdapterService } from './toast-adapter.service';
-import { SkyToastInstance } from '.';
-import { SkyToast } from './toast';
+import {
+ SkyToastInstance
+} from './toast-instance';
+
+import {
+ SkyToastService
+} from './toast.service';
// #endregion
describe('Toast service', () => {
diff --git a/src/modules/toast/toast.service.ts b/src/modules/toast/toast.service.ts
index 8888b2f3a..3b56306b2 100644
--- a/src/modules/toast/toast.service.ts
+++ b/src/modules/toast/toast.service.ts
@@ -18,14 +18,14 @@ import {
Observable
} from 'rxjs/Observable';
-import {
- SkyToast
-} from './toast';
-
import {
SkyToastConfig
} from './types';
+import {
+ SkyToast
+} from './toast';
+
import {
SkyToastAdapterService
} from './toast-adapter.service';
@@ -71,6 +71,7 @@ export class SkyToastService implements OnDestroy {
this._toastStream.complete();
}
+ // TODO: Jsdoc here!
public openMessage(
message: string,
config?: SkyToastConfig
@@ -86,6 +87,7 @@ export class SkyToastService implements OnDestroy {
return this.openComponent(SkyToastBodyComponent, config, providers);
}
+ // TODO: Jsdoc here!
public openComponent(
component: any,
config?: SkyToastConfig,
@@ -105,6 +107,7 @@ export class SkyToastService implements OnDestroy {
return instance;
}
+ // TODO: Jsdoc here!
public closeAll(): void {
this.toasts.forEach(toast => toast.instance.close());
this.toasts = [];
diff --git a/src/modules/toast/toast.spec.ts b/src/modules/toast/toast.spec.ts
index 7868192a7..d7d9db229 100644
--- a/src/modules/toast/toast.spec.ts
+++ b/src/modules/toast/toast.spec.ts
@@ -3,9 +3,21 @@ import {
expect
} from '@blackbaud/skyux-builder/runtime/testing/browser';
-import {Provider } from '@angular/core';
-import { SkyToast } from './toast';
-import { SkyToastConfig, SkyToastInstance } from '.';
+import {
+ Provider
+} from '@angular/core';
+
+import {
+ SkyToastConfig
+} from './types';
+
+import {
+ SkyToast
+} from './toast';
+
+import {
+ SkyToastInstance
+} from './toast-instance';
// #endregion
describe('Toast class', () => {
diff --git a/src/modules/toast/toaster.component.scss b/src/modules/toast/toaster.component.scss
index 3cd01dfc4..8ed97ed24 100644
--- a/src/modules/toast/toaster.component.scss
+++ b/src/modules/toast/toaster.component.scss
@@ -7,7 +7,7 @@
position: fixed;
padding-bottom: $sky-margin-double;
padding-right: $sky-margin-double;
- width: 300px;
+ max-width: 300px;
-webkit-touch-callout: none; /* iOS Safari */
-webkit-user-select: none; /* Safari */
-khtml-user-select: none; /* Konqueror HTML */
diff --git a/src/modules/toast/toaster.component.spec.ts b/src/modules/toast/toaster.component.spec.ts
index 0f65010dd..d76702745 100644
--- a/src/modules/toast/toaster.component.spec.ts
+++ b/src/modules/toast/toaster.component.spec.ts
@@ -81,7 +81,11 @@ describe('Toast component', () => {
provide: SkyToastBodyTestContext,
useValue: new SkyToastBodyTestContext(message)
}];
- const instance = toastService.openComponent(SkyToastBodyTestComponent, {}, providers);
+ const instance = toastService.openComponent(
+ SkyToastBodyTestComponent,
+ {},
+ providers
+ );
fixture.detectChanges();
tick();
return instance;
@@ -143,53 +147,3 @@ describe('Toast component', () => {
expect(toasts.length).toEqual(0);
}));
});
-
-// import {
-// TestBed
-// } from '@angular/core/testing';
-
-// import {
-// BehaviorSubject
-// } from 'rxjs';
-
-// import {
-// SkyToastService,
-// SkyToasterComponent
-// } from '.';
-// import {
-// SkyToastInstance
-// } from './types';
-
-// describe('Toaster component', () => {
-// let toastService: SkyToastService;
-// let toastInstances: BehaviorSubject = new BehaviorSubject([]);
-
-// beforeEach(() => {
-// TestBed.configureTestingModule({
-// providers: [
-// {
-// provide: SkyToastService,
-// useValue: {
-// toastInstances: toastInstances
-// }
-// }
-// ]});
-// toastService = TestBed.get(SkyToastService);
-// });
-
-// it('should instantiate a toaster with its own subscription to the toastInstance list',
-// (done: Function) => {
-// let instance: SkyToastInstance = new SkyToastInstance('My message', undefined, 'danger', []);
-// instance.isClosed.subscribe(() => {
-// toastInstances.next([]);
-// });
-// toastInstances.next([instance]);
-
-// let container: SkyToasterComponent = new SkyToasterComponent(toastService);
-// container.ngOnInit();
-// container.toastInstances.subscribe((value) => {
-// expect(value[0]).toBe(instance);
-// done();
-// });
-// });
-// });
From 62458b7423fa9b31124d8cfa73c3edf7b31d591b Mon Sep 17 00:00:00 2001
From: Blackbaud-SteveBrush
Date: Thu, 10 May 2018 14:35:45 -0400
Subject: [PATCH 33/46] Updated visual tests
---
.../src/app/toast/toast-visual.component.html | 8 +++++---
skyux-spa-visual-tests/src/app/toast/toast.visual-spec.ts | 8 ++++----
2 files changed, 9 insertions(+), 7 deletions(-)
diff --git a/skyux-spa-visual-tests/src/app/toast/toast-visual.component.html b/skyux-spa-visual-tests/src/app/toast/toast-visual.component.html
index 093bec4b2..06ebeec29 100644
--- a/skyux-spa-visual-tests/src/app/toast/toast-visual.component.html
+++ b/skyux-spa-visual-tests/src/app/toast/toast-visual.component.html
@@ -1,16 +1,18 @@
+>
+ (click)="openToast()"
+ >
Open toast
+ (click)="openComponent()"
+ >
Open custom toast
diff --git a/skyux-spa-visual-tests/src/app/toast/toast.visual-spec.ts b/skyux-spa-visual-tests/src/app/toast/toast.visual-spec.ts
index 8d66e651e..dcf338a24 100644
--- a/skyux-spa-visual-tests/src/app/toast/toast.visual-spec.ts
+++ b/skyux-spa-visual-tests/src/app/toast/toast.visual-spec.ts
@@ -15,7 +15,7 @@ describe('Toast', () => {
SkyVisualTest.moveCursorOffScreen();
return SkyVisualTest.compareScreenshot({
screenshotName: 'toast',
- selector: 'body'
+ selector: '.sky-toaster'
});
});
});
@@ -27,7 +27,7 @@ describe('Toast', () => {
SkyVisualTest.moveCursorOffScreen();
return SkyVisualTest.compareScreenshot({
screenshotName: 'toast-component',
- selector: 'body'
+ selector: '.sky-toaster'
});
});
});
@@ -39,7 +39,7 @@ describe('Toast', () => {
SkyVisualTest.moveCursorOffScreen();
return SkyVisualTest.compareScreenshot({
screenshotName: 'toast-xs',
- selector: 'body'
+ selector: '.sky-toaster'
});
});
});
@@ -51,7 +51,7 @@ describe('Toast', () => {
SkyVisualTest.moveCursorOffScreen();
return SkyVisualTest.compareScreenshot({
screenshotName: 'toast-component-xs',
- selector: 'body'
+ selector: '.sky-toaster'
});
});
});
From 393c00cd0fb84d93daa611dbb12d29ce901033fa Mon Sep 17 00:00:00 2001
From: Blackbaud-SteveBrush
Date: Thu, 10 May 2018 14:47:24 -0400
Subject: [PATCH 34/46] Added jsdoc
---
src/modules/toast/toast-instance.ts | 6 ++++--
src/modules/toast/toast.service.ts | 17 ++++++++++++++---
2 files changed, 18 insertions(+), 5 deletions(-)
diff --git a/src/modules/toast/toast-instance.ts b/src/modules/toast/toast-instance.ts
index 0d0202864..8fb3879ea 100644
--- a/src/modules/toast/toast-instance.ts
+++ b/src/modules/toast/toast-instance.ts
@@ -15,8 +15,10 @@ export class SkyToastInstance {
private _closed = new EventEmitter();
- // TODO: Jsdoc here!
- public close() {
+ /**
+ * Closes the toast component.
+ */
+ public close(): void {
this._closed.emit();
this._closed.complete();
}
diff --git a/src/modules/toast/toast.service.ts b/src/modules/toast/toast.service.ts
index 3b56306b2..a4f1b4fab 100644
--- a/src/modules/toast/toast.service.ts
+++ b/src/modules/toast/toast.service.ts
@@ -71,7 +71,11 @@ export class SkyToastService implements OnDestroy {
this._toastStream.complete();
}
- // TODO: Jsdoc here!
+ /**
+ * Opens a new toast with a text message.
+ * @param message Text to display inside the toast
+ * @param config Optional configuration
+ */
public openMessage(
message: string,
config?: SkyToastConfig
@@ -87,7 +91,12 @@ export class SkyToastService implements OnDestroy {
return this.openComponent(SkyToastBodyComponent, config, providers);
}
- // TODO: Jsdoc here!
+ /**
+ * Opens a new toast using a custom component.
+ * @param component Angular component to inject into the toast body
+ * @param config Optional configuration
+ * @param providers Optional providers for the custom component
+ */
public openComponent(
component: any,
config?: SkyToastConfig,
@@ -107,7 +116,9 @@ export class SkyToastService implements OnDestroy {
return instance;
}
- // TODO: Jsdoc here!
+ /**
+ * Closes all active toast components.
+ */
public closeAll(): void {
this.toasts.forEach(toast => toast.instance.close());
this.toasts = [];
From ab1053a3c877721a54dfce0f9279bfcb11ec7d0c Mon Sep 17 00:00:00 2001
From: Blackbaud-SteveBrush
Date: Thu, 10 May 2018 15:10:15 -0400
Subject: [PATCH 35/46] Final adjustments
---
.../toast/toast-custom-demo.component.ts | 5 ++-
.../toast-body.component.fixture.html | 4 +--
src/modules/toast/toast.component.scss | 34 ++++++++-----------
3 files changed, 20 insertions(+), 23 deletions(-)
diff --git a/src/demos/toast/toast-custom-demo.component.ts b/src/demos/toast/toast-custom-demo.component.ts
index 32caf416a..cd34514d4 100644
--- a/src/demos/toast/toast-custom-demo.component.ts
+++ b/src/demos/toast/toast-custom-demo.component.ts
@@ -6,7 +6,10 @@ import {
import {
SkyToastInstance
} from '../../core';
-import { SkyToastCustomDemoContext } from './toast-custom-demo-context';
+
+import {
+ SkyToastCustomDemoContext
+} from './toast-custom-demo-context';
@Component({
selector: 'sky-toast-custom-demo',
diff --git a/src/modules/toast/fixtures/toast-body.component.fixture.html b/src/modules/toast/fixtures/toast-body.component.fixture.html
index f1a8009c1..8b69eb26b 100644
--- a/src/modules/toast/fixtures/toast-body.component.fixture.html
+++ b/src/modules/toast/fixtures/toast-body.component.fixture.html
@@ -1,6 +1,4 @@
-
+
{{ context.message }}
Date: Fri, 11 May 2018 15:34:55 -0400
Subject: [PATCH 36/46] Removed unused toastr dependency
---
package.json | 1 -
1 file changed, 1 deletion(-)
diff --git a/package.json b/package.json
index 23a295fcd..8de7bebf4 100644
--- a/package.json
+++ b/package.json
@@ -46,7 +46,6 @@
"microedge-rxstate": "2.0.2",
"moment": "2.21.0",
"ng2-dragula": "1.5.0",
- "ng2-toastr": "4.1.2",
"web-animations-js": "2.3.1"
},
"devDependencies": {
From 25423a2264c40097f88f552b11005289593fef3d Mon Sep 17 00:00:00 2001
From: Blackbaud-SteveBrush
Date: Mon, 14 May 2018 10:25:07 -0400
Subject: [PATCH 37/46] Running compact logger for visual tests
---
skyux-spa-visual-tests/config/utils/start-visual.js | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/skyux-spa-visual-tests/config/utils/start-visual.js b/skyux-spa-visual-tests/config/utils/start-visual.js
index a999a68cd..4a03e1910 100644
--- a/skyux-spa-visual-tests/config/utils/start-visual.js
+++ b/skyux-spa-visual-tests/config/utils/start-visual.js
@@ -191,7 +191,11 @@ function spawnServer() {
function spawnBuild(skyPagesConfig, webpack) {
return new Promise(resolve => {
logger.info('Running build');
- build([], skyPagesConfig, webpack).then(stats => {
+ build(
+ { logFormat: 'compact' },
+ skyPagesConfig,
+ webpack
+ ).then(stats => {
logger.info('Completed build');
resolve(stats.toJson().chunks);
});
From c78585b2e134ae7bfd50050f8a7a45762e9f6c50 Mon Sep 17 00:00:00 2001
From: Blackbaud-SteveBrush
Date: Fri, 18 May 2018 09:52:11 -0400
Subject: [PATCH 38/46] Converted to enum type
---
src/demos/toast/toast-demo.component.ts | 10 +++++-----
src/modules/toast/toast.component.scss | 4 ----
src/modules/toast/toast.component.spec.ts | 10 +++++-----
src/modules/toast/toast.component.ts | 2 +-
src/modules/toast/toaster.component.scss | 6 ------
src/modules/toast/types/toast-type.ts | 7 ++++++-
6 files changed, 17 insertions(+), 22 deletions(-)
diff --git a/src/demos/toast/toast-demo.component.ts b/src/demos/toast/toast-demo.component.ts
index aa7e37c3d..6e5839ed5 100644
--- a/src/demos/toast/toast-demo.component.ts
+++ b/src/demos/toast/toast-demo.component.ts
@@ -20,12 +20,12 @@ import {
templateUrl: './toast-demo.component.html'
})
export class SkyToastDemoComponent {
- public selectedType: SkyToastType = 'info';
+ public selectedType: SkyToastType = SkyToastType.Info;
public types: SkyToastType[] = [
- 'info',
- 'success',
- 'warning',
- 'danger'
+ SkyToastType.Info,
+ SkyToastType.Success,
+ SkyToastType.Warning,
+ SkyToastType.Danger
];
constructor(
diff --git a/src/modules/toast/toast.component.scss b/src/modules/toast/toast.component.scss
index f725f7bd0..3b626150b 100644
--- a/src/modules/toast/toast.component.scss
+++ b/src/modules/toast/toast.component.scss
@@ -11,10 +11,6 @@
align-items: center;
opacity: 1;
- &:hover {
- @include sky-shadow;
- }
-
button {
margin-left: auto;
width: 32px;
diff --git a/src/modules/toast/toast.component.spec.ts b/src/modules/toast/toast.component.spec.ts
index e18da1a44..4a88edf30 100644
--- a/src/modules/toast/toast.component.spec.ts
+++ b/src/modules/toast/toast.component.spec.ts
@@ -52,15 +52,15 @@ describe('Toast component', () => {
}
it('should set defaults', () => {
- expect(toastComponent.toastType).toEqual('info');
+ expect(toastComponent.toastType).toEqual(SkyToastType.Info);
});
it('should allow setting the toast type', () => {
verifyType(); // default
- verifyType('info');
- verifyType('success');
- verifyType('warning');
- verifyType('danger');
+ verifyType(SkyToastType.Info);
+ verifyType(SkyToastType.Success);
+ verifyType(SkyToastType.Warning);
+ verifyType(SkyToastType.Danger);
});
it('should close the toast when clicking close button', () => {
diff --git a/src/modules/toast/toast.component.ts b/src/modules/toast/toast.component.ts
index 9d9bd513b..adcd40124 100644
--- a/src/modules/toast/toast.component.ts
+++ b/src/modules/toast/toast.component.ts
@@ -49,7 +49,7 @@ export class SkyToastComponent implements OnInit {
}
public get toastType(): SkyToastType {
- return this._toastType || 'info';
+ return this._toastType || SkyToastType.Info;
}
@Output()
diff --git a/src/modules/toast/toaster.component.scss b/src/modules/toast/toaster.component.scss
index 8ed97ed24..83b1ffa3f 100644
--- a/src/modules/toast/toaster.component.scss
+++ b/src/modules/toast/toaster.component.scss
@@ -8,10 +8,4 @@
padding-bottom: $sky-margin-double;
padding-right: $sky-margin-double;
max-width: 300px;
- -webkit-touch-callout: none; /* iOS Safari */
- -webkit-user-select: none; /* Safari */
- -khtml-user-select: none; /* Konqueror HTML */
- -moz-user-select: none; /* Firefox */
- -ms-user-select: none; /* Internet Explorer/Edge */
- user-select: none; /* Chrom and Opera */
}
diff --git a/src/modules/toast/types/toast-type.ts b/src/modules/toast/types/toast-type.ts
index 590bb6a1a..395f8c238 100644
--- a/src/modules/toast/types/toast-type.ts
+++ b/src/modules/toast/types/toast-type.ts
@@ -1 +1,6 @@
-export type SkyToastType = 'info' | 'success' | 'warning' | 'danger';
+export enum SkyToastType {
+ Info = 'info',
+ Success = 'success',
+ Warning = 'warning',
+ Danger = 'danger'
+}
From bc23548561b33aef5f97d76fb29bb8e1a7035708 Mon Sep 17 00:00:00 2001
From: Blackbaud-SteveBrush
Date: Fri, 18 May 2018 15:41:12 -0400
Subject: [PATCH 39/46] Created backwards compatible string enum
---
src/modules/toast/toast.service.spec.ts | 10 +++++++---
src/modules/toast/types/toast-type.ts | 15 ++++++++++-----
2 files changed, 17 insertions(+), 8 deletions(-)
diff --git a/src/modules/toast/toast.service.spec.ts b/src/modules/toast/toast.service.spec.ts
index eae0c5a69..67af63ac9 100644
--- a/src/modules/toast/toast.service.spec.ts
+++ b/src/modules/toast/toast.service.spec.ts
@@ -32,6 +32,10 @@ import {
import {
SkyToastService
} from './toast.service';
+
+import {
+ SkyToastType
+} from './types';
// #endregion
describe('Toast service', () => {
@@ -93,7 +97,7 @@ describe('Toast service', () => {
describe('openMessage() method', () => {
it('should open a toast with the given message and configuration', function() {
const instance = toastService.openMessage('Real message', {
- type: 'danger'
+ type: SkyToastType.Danger
});
expect(instance).toBeTruthy();
@@ -153,7 +157,7 @@ describe('Toast service', () => {
const instance = toastService.openComponent(
TestComponent,
{
- type: 'danger'
+ type: SkyToastType.Danger
},
[providers]
);
@@ -175,7 +179,7 @@ describe('Toast service', () => {
it('should handle empty providers', () => {
toastService.openComponent(TestComponent, {
- type: 'danger'
+ type: SkyToastType.Danger
});
toastService.toastStream.take(1).subscribe((toasts: SkyToast[]) => {
diff --git a/src/modules/toast/types/toast-type.ts b/src/modules/toast/types/toast-type.ts
index 395f8c238..8a7c813b8 100644
--- a/src/modules/toast/types/toast-type.ts
+++ b/src/modules/toast/types/toast-type.ts
@@ -1,6 +1,11 @@
-export enum SkyToastType {
- Info = 'info',
- Success = 'success',
- Warning = 'warning',
- Danger = 'danger'
+// TODO: Convert to string enum after upgrading TypeScript to ^2.4.0.
+// https://stackoverflow.com/questions/45191472/typescripts-string-enums-type-is-not-assignable-to-type
+type ToastType = 'danger' | 'info' | 'success' | 'warning';
+
+// tslint:disable:variable-name
+export abstract class SkyToastType {
+ public static Danger: ToastType = 'danger';
+ public static Info: ToastType = 'info';
+ public static Success: ToastType = 'success';
+ public static Warning: ToastType = 'warning';
}
From 1e20c13941e6390ba87b9b4772a2699aa36f3000 Mon Sep 17 00:00:00 2001
From: Blackbaud-SteveBrush
Date: Fri, 18 May 2018 17:12:39 -0400
Subject: [PATCH 40/46] Added animation and scrollbar
---
src/demos/toast/toast-demo.component.ts | 10 ++--
src/modules/animation/emerge.spec.ts | 16 ++++++
src/modules/animation/emerge.ts | 29 ++++++++++
src/modules/animation/index.ts | 2 +
src/modules/toast/toast.component.html | 4 +-
src/modules/toast/toast.component.scss | 65 +++++++++++------------
src/modules/toast/toast.component.spec.ts | 14 ++---
src/modules/toast/toast.component.ts | 33 ++++--------
src/modules/toast/toast.service.spec.ts | 10 ++--
src/modules/toast/toaster.component.html | 1 +
src/modules/toast/toaster.component.scss | 5 +-
src/modules/toast/toaster.component.ts | 24 ++++++++-
src/modules/toast/types/toast-type.ts | 10 +---
13 files changed, 134 insertions(+), 89 deletions(-)
create mode 100644 src/modules/animation/emerge.spec.ts
create mode 100644 src/modules/animation/emerge.ts
create mode 100644 src/modules/animation/index.ts
diff --git a/src/demos/toast/toast-demo.component.ts b/src/demos/toast/toast-demo.component.ts
index 6e5839ed5..aa7e37c3d 100644
--- a/src/demos/toast/toast-demo.component.ts
+++ b/src/demos/toast/toast-demo.component.ts
@@ -20,12 +20,12 @@ import {
templateUrl: './toast-demo.component.html'
})
export class SkyToastDemoComponent {
- public selectedType: SkyToastType = SkyToastType.Info;
+ public selectedType: SkyToastType = 'info';
public types: SkyToastType[] = [
- SkyToastType.Info,
- SkyToastType.Success,
- SkyToastType.Warning,
- SkyToastType.Danger
+ 'info',
+ 'success',
+ 'warning',
+ 'danger'
];
constructor(
diff --git a/src/modules/animation/emerge.spec.ts b/src/modules/animation/emerge.spec.ts
new file mode 100644
index 000000000..2eced1093
--- /dev/null
+++ b/src/modules/animation/emerge.spec.ts
@@ -0,0 +1,16 @@
+// #region imports
+import {
+ skyAnimationEmerge
+} from './emerge';
+// #endregion
+
+describe('Animation emerge', () => {
+ it('should define an animation trigger', () => {
+ const definitions: any = skyAnimationEmerge.definitions;
+
+ expect(skyAnimationEmerge.name).toEqual('skyAnimationEmerge');
+ expect(definitions[0].name).toEqual('open');
+ expect(definitions[1].name).toEqual('closed');
+ expect(definitions[2].animation.timings).toEqual('300ms ease-in-out');
+ });
+});
diff --git a/src/modules/animation/emerge.ts b/src/modules/animation/emerge.ts
new file mode 100644
index 000000000..d798d34af
--- /dev/null
+++ b/src/modules/animation/emerge.ts
@@ -0,0 +1,29 @@
+// #region imports
+import {
+ animate,
+ AnimationEntryMetadata,
+ state,
+ style,
+ transition,
+ trigger
+} from '@angular/core';
+// #endregion
+
+export const skyAnimationEmerge = trigger('skyAnimationEmerge', [
+ state('open', style({
+ opacity: 1,
+ transform: 'scale(1)'
+ })),
+ state('closed', style({
+ opacity: 0,
+ transform: 'scale(0.0)'
+ })),
+ transition('void => *', [
+ style({
+ opacity: 0,
+ transform: 'scale(0.0)'
+ }),
+ animate('300ms ease-in-out')
+ ]),
+ transition(`* <=> *`, animate('300ms ease-in-out'))
+]) as AnimationEntryMetadata;
diff --git a/src/modules/animation/index.ts b/src/modules/animation/index.ts
new file mode 100644
index 000000000..aa0000d43
--- /dev/null
+++ b/src/modules/animation/index.ts
@@ -0,0 +1,2 @@
+export * from './emerge';
+export * from './slide';
diff --git a/src/modules/toast/toast.component.html b/src/modules/toast/toast.component.html
index 4415e64fc..6aa0f501e 100644
--- a/src/modules/toast/toast.component.html
+++ b/src/modules/toast/toast.component.html
@@ -1,6 +1,6 @@
{
}
it('should set defaults', () => {
- expect(toastComponent.toastType).toEqual(SkyToastType.Info);
+ expect(toastComponent.toastType).toEqual('info');
});
it('should allow setting the toast type', () => {
verifyType(); // default
- verifyType(SkyToastType.Info);
- verifyType(SkyToastType.Success);
- verifyType(SkyToastType.Warning);
- verifyType(SkyToastType.Danger);
+ verifyType('info');
+ verifyType('success');
+ verifyType('warning');
+ verifyType('danger');
});
it('should close the toast when clicking close button', () => {
fixture.detectChanges();
expect(toastComponent['isOpen']).toEqual(true);
- expect(toastComponent.getAnimationState()).toEqual('toastOpen');
+ expect(toastComponent.animationState).toEqual('open');
fixture.nativeElement.querySelector('.sky-toast-btn-close').click();
fixture.detectChanges();
expect(toastComponent['isOpen']).toEqual(false);
- expect(toastComponent.getAnimationState()).toEqual('toastClosed');
+ expect(toastComponent.animationState).toEqual('closed');
});
});
diff --git a/src/modules/toast/toast.component.ts b/src/modules/toast/toast.component.ts
index adcd40124..c378b7725 100644
--- a/src/modules/toast/toast.component.ts
+++ b/src/modules/toast/toast.component.ts
@@ -10,35 +10,24 @@ import {
} from '@angular/core';
import {
- trigger,
- state,
- style,
- animate,
- transition,
AnimationEvent
} from '@angular/animations';
+import {
+ skyAnimationEmerge
+} from '../animation';
+
import {
SkyToastType
} from './types';
// #endregion
-const TOAST_OPEN_STATE = 'toastOpen';
-const TOAST_CLOSED_STATE = 'toastClosed';
-
@Component({
selector: 'sky-toast',
templateUrl: './toast.component.html',
styleUrls: ['./toast.component.scss'],
animations: [
- trigger('toastState', [
- state(TOAST_OPEN_STATE, style({ opacity: 1 })),
- state(TOAST_CLOSED_STATE, style({ opacity: 0 })),
- transition(
- `${TOAST_OPEN_STATE} => ${TOAST_CLOSED_STATE}`,
- animate('150ms linear')
- )
- ])
+ skyAnimationEmerge
],
changeDetection: ChangeDetectionStrategy.OnPush
})
@@ -49,12 +38,16 @@ export class SkyToastComponent implements OnInit {
}
public get toastType(): SkyToastType {
- return this._toastType || SkyToastType.Info;
+ return this._toastType || 'info';
}
@Output()
public closed = new EventEmitter
();
+ public get animationState(): string {
+ return (this.isOpen) ? 'open' : 'closed';
+ }
+
private isOpen = false;
private _toastType: SkyToastType;
@@ -67,12 +60,8 @@ export class SkyToastComponent implements OnInit {
this.isOpen = true;
}
- public getAnimationState(): string {
- return (this.isOpen) ? TOAST_OPEN_STATE : TOAST_CLOSED_STATE;
- }
-
public onAnimationDone(event: AnimationEvent) {
- if (event.toState === TOAST_CLOSED_STATE) {
+ if (event.toState === 'closed') {
this.closed.emit();
this.closed.complete();
}
diff --git a/src/modules/toast/toast.service.spec.ts b/src/modules/toast/toast.service.spec.ts
index 67af63ac9..eae0c5a69 100644
--- a/src/modules/toast/toast.service.spec.ts
+++ b/src/modules/toast/toast.service.spec.ts
@@ -32,10 +32,6 @@ import {
import {
SkyToastService
} from './toast.service';
-
-import {
- SkyToastType
-} from './types';
// #endregion
describe('Toast service', () => {
@@ -97,7 +93,7 @@ describe('Toast service', () => {
describe('openMessage() method', () => {
it('should open a toast with the given message and configuration', function() {
const instance = toastService.openMessage('Real message', {
- type: SkyToastType.Danger
+ type: 'danger'
});
expect(instance).toBeTruthy();
@@ -157,7 +153,7 @@ describe('Toast service', () => {
const instance = toastService.openComponent(
TestComponent,
{
- type: SkyToastType.Danger
+ type: 'danger'
},
[providers]
);
@@ -179,7 +175,7 @@ describe('Toast service', () => {
it('should handle empty providers', () => {
toastService.openComponent(TestComponent, {
- type: SkyToastType.Danger
+ type: 'danger'
});
toastService.toastStream.take(1).subscribe((toasts: SkyToast[]) => {
diff --git a/src/modules/toast/toaster.component.html b/src/modules/toast/toaster.component.html
index 71f61e7c8..16ba51e6a 100644
--- a/src/modules/toast/toaster.component.html
+++ b/src/modules/toast/toaster.component.html
@@ -1,5 +1,6 @@
{
return this.toastService.toastStream;
}
+ @ViewChild('toaster')
+ private toaster: ElementRef;
+
@ViewChildren('toastContent', { read: ViewContainerRef })
private toastContent: QueryList;
constructor(
private toastService: SkyToastService,
private resolver: ComponentFactoryResolver,
- private injector: Injector
+ private injector: Injector,
+ private windowRef: SkyWindowRefService
) { }
public ngAfterViewInit(): void {
@@ -51,6 +64,13 @@ export class SkyToasterComponent implements AfterViewInit {
this.toastContent.changes.subscribe(() => {
this.injectToastContent();
});
+
+ // Scroll to the bottom of the toaster element when a new toast is added.
+ this.toastStream.subscribe((toasts: SkyToast[]) => {
+ this.windowRef.getWindow().setTimeout(() => {
+ this.toaster.nativeElement.scrollTop = this.toaster.nativeElement.scrollHeight;
+ });
+ });
}
public onToastClosed(toast: SkyToast): void {
diff --git a/src/modules/toast/types/toast-type.ts b/src/modules/toast/types/toast-type.ts
index 8a7c813b8..4a1303800 100644
--- a/src/modules/toast/types/toast-type.ts
+++ b/src/modules/toast/types/toast-type.ts
@@ -1,11 +1,3 @@
// TODO: Convert to string enum after upgrading TypeScript to ^2.4.0.
// https://stackoverflow.com/questions/45191472/typescripts-string-enums-type-is-not-assignable-to-type
-type ToastType = 'danger' | 'info' | 'success' | 'warning';
-
-// tslint:disable:variable-name
-export abstract class SkyToastType {
- public static Danger: ToastType = 'danger';
- public static Info: ToastType = 'info';
- public static Success: ToastType = 'success';
- public static Warning: ToastType = 'warning';
-}
+export type SkyToastType = 'danger' | 'info' | 'success' | 'warning';
From a2fb02de0606c2792d6b5275648b1ecf77a333e2 Mon Sep 17 00:00:00 2001
From: Blackbaud-SteveBrush
Date: Fri, 18 May 2018 17:22:30 -0400
Subject: [PATCH 41/46] Fixed unit test for animation
---
src/modules/animation/emerge.spec.ts | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/src/modules/animation/emerge.spec.ts b/src/modules/animation/emerge.spec.ts
index 2eced1093..f0148073b 100644
--- a/src/modules/animation/emerge.spec.ts
+++ b/src/modules/animation/emerge.spec.ts
@@ -7,10 +7,12 @@ import {
describe('Animation emerge', () => {
it('should define an animation trigger', () => {
const definitions: any = skyAnimationEmerge.definitions;
-
expect(skyAnimationEmerge.name).toEqual('skyAnimationEmerge');
expect(definitions[0].name).toEqual('open');
expect(definitions[1].name).toEqual('closed');
- expect(definitions[2].animation.timings).toEqual('300ms ease-in-out');
+ expect(definitions[2].expr).toEqual('void => *');
+ expect(definitions[2].animation[1].timings).toEqual('300ms ease-in-out');
+ expect(definitions[3].expr).toEqual('* <=> *');
+ expect(definitions[3].animation.timings).toEqual('300ms ease-in-out');
});
});
From d0fb337949a5c93765db5d1f8e1678bf2b27a74d Mon Sep 17 00:00:00 2001
From: Blackbaud-SteveBrush
Date: Fri, 18 May 2018 17:32:51 -0400
Subject: [PATCH 42/46] Added scrollBottom method to adapter
---
src/modules/toast/toast-adapter.service.ts | 8 ++++++++
src/modules/toast/toaster.component.ts | 21 ++++++++-------------
2 files changed, 16 insertions(+), 13 deletions(-)
diff --git a/src/modules/toast/toast-adapter.service.ts b/src/modules/toast/toast-adapter.service.ts
index 667534ec1..99ec53934 100644
--- a/src/modules/toast/toast-adapter.service.ts
+++ b/src/modules/toast/toast-adapter.service.ts
@@ -1,5 +1,6 @@
// #region imports
import {
+ ElementRef,
Injectable,
Renderer2,
RendererFactory2
@@ -32,4 +33,11 @@ export class SkyToastAdapterService {
const document = this.windowRef.getWindow().document;
this.renderer.removeChild(document.body, this.hostElement);
}
+
+ public scrollBottom(elementRef: ElementRef): void {
+ const element = elementRef.nativeElement;
+ this.windowRef.getWindow().setTimeout(() => {
+ element.scrollTop = element.scrollHeight;
+ });
+ }
}
diff --git a/src/modules/toast/toaster.component.ts b/src/modules/toast/toaster.component.ts
index 599e9cf97..d9ee4f97b 100644
--- a/src/modules/toast/toaster.component.ts
+++ b/src/modules/toast/toaster.component.ts
@@ -19,14 +19,14 @@ import {
import 'rxjs/add/operator/take';
-import {
- SkyWindowRefService
-} from '../../../dist/modules/window';
-
import {
SkyToast
} from './toast';
+import {
+ SkyToastAdapterService
+} from './toast-adapter.service';
+
import {
SkyToastService
} from './toast.service';
@@ -36,10 +36,7 @@ import {
selector: 'sky-toaster',
templateUrl: './toaster.component.html',
styleUrls: ['./toaster.component.scss'],
- changeDetection: ChangeDetectionStrategy.OnPush,
- providers: [
- SkyWindowRefService
- ]
+ changeDetection: ChangeDetectionStrategy.OnPush
})
export class SkyToasterComponent implements AfterViewInit {
public get toastStream(): Observable {
@@ -53,10 +50,10 @@ export class SkyToasterComponent implements AfterViewInit {
private toastContent: QueryList;
constructor(
+ private domAdapter: SkyToastAdapterService,
private toastService: SkyToastService,
private resolver: ComponentFactoryResolver,
- private injector: Injector,
- private windowRef: SkyWindowRefService
+ private injector: Injector
) { }
public ngAfterViewInit(): void {
@@ -67,9 +64,7 @@ export class SkyToasterComponent implements AfterViewInit {
// Scroll to the bottom of the toaster element when a new toast is added.
this.toastStream.subscribe((toasts: SkyToast[]) => {
- this.windowRef.getWindow().setTimeout(() => {
- this.toaster.nativeElement.scrollTop = this.toaster.nativeElement.scrollHeight;
- });
+ this.domAdapter.scrollBottom(this.toaster);
});
}
From e2aeb308bb3b22c47e161714fdf40df31f39f63e Mon Sep 17 00:00:00 2001
From: Blackbaud-SteveBrush
Date: Sat, 19 May 2018 15:52:44 -0400
Subject: [PATCH 43/46] Adjusted animation imports
---
src/modules/animation/emerge.ts | 7 +++++--
src/modules/animation/slide.ts | 9 +++++++--
2 files changed, 12 insertions(+), 4 deletions(-)
diff --git a/src/modules/animation/emerge.ts b/src/modules/animation/emerge.ts
index d798d34af..eb121f5c8 100644
--- a/src/modules/animation/emerge.ts
+++ b/src/modules/animation/emerge.ts
@@ -1,12 +1,15 @@
// #region imports
+import {
+ AnimationEntryMetadata
+} from '@angular/core';
+
import {
animate,
- AnimationEntryMetadata,
state,
style,
transition,
trigger
-} from '@angular/core';
+} from '@angular/animations';
// #endregion
export const skyAnimationEmerge = trigger('skyAnimationEmerge', [
diff --git a/src/modules/animation/slide.ts b/src/modules/animation/slide.ts
index 9e8bb81c0..0a033def6 100644
--- a/src/modules/animation/slide.ts
+++ b/src/modules/animation/slide.ts
@@ -1,11 +1,16 @@
+// #region imports
+import {
+ AnimationEntryMetadata
+} from '@angular/core';
+
import {
animate,
- AnimationEntryMetadata,
state,
style,
transition,
trigger
-} from '@angular/core';
+} from '@angular/animations';
+// #endregion
export const skyAnimationSlide = trigger('skyAnimationSlide', [
state('down', style({
From b0e50e3a5ae357ca31592c5be22afbbf1b0accd8 Mon Sep 17 00:00:00 2001
From: Blackbaud-SteveBrush
Date: Mon, 21 May 2018 11:36:37 -0400
Subject: [PATCH 44/46] Converted to num enum
---
.../src/app/toast/toast-visual.component.html | 8 ++--
.../src/app/toast/toast-visual.component.ts | 26 ++++++++----
src/demos/toast/toast-demo.component.html | 4 +-
src/demos/toast/toast-demo.component.ts | 26 ++++++++----
src/modules/toast/toast.component.html | 6 +--
src/modules/toast/toast.component.spec.ts | 31 ++++++++++----
src/modules/toast/toast.component.ts | 40 ++++++++++++++++++-
src/modules/toast/toast.service.spec.ts | 10 +++--
src/modules/toast/types/toast-type.ts | 9 +++--
9 files changed, 122 insertions(+), 38 deletions(-)
diff --git a/skyux-spa-visual-tests/src/app/toast/toast-visual.component.html b/skyux-spa-visual-tests/src/app/toast/toast-visual.component.html
index 06ebeec29..be0c6dec3 100644
--- a/skyux-spa-visual-tests/src/app/toast/toast-visual.component.html
+++ b/skyux-spa-visual-tests/src/app/toast/toast-visual.component.html
@@ -4,15 +4,15 @@
- Open toast
+ Open toasts
- Open custom toast
+ Open custom toasts
diff --git a/skyux-spa-visual-tests/src/app/toast/toast-visual.component.ts b/skyux-spa-visual-tests/src/app/toast/toast-visual.component.ts
index 05e4f7a79..534d35c4b 100644
--- a/skyux-spa-visual-tests/src/app/toast/toast-visual.component.ts
+++ b/skyux-spa-visual-tests/src/app/toast/toast-visual.component.ts
@@ -1,10 +1,12 @@
import {
Component,
- ChangeDetectionStrategy
+ ChangeDetectionStrategy,
+ OnDestroy
} from '@angular/core';
import {
- SkyToastService
+ SkyToastService,
+ SkyToastType
} from '@blackbaud/skyux/dist/core';
import {
@@ -16,16 +18,26 @@ import {
templateUrl: './toast-visual.component.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
-export class ToastVisualComponent {
+export class ToastVisualComponent implements OnDestroy {
constructor(
private toastService: SkyToastService
) { }
- public openToast() {
- this.toastService.openMessage('Toast message');
+ public ngOnDestroy(): void {
+ this.toastService.closeAll();
}
- public openComponent() {
- this.toastService.openComponent(ToastDemoComponent, {});
+ public openToasts(): void {
+ this.toastService.openMessage('Toast message', { type: SkyToastType.Info });
+ this.toastService.openMessage('Toast message', { type: SkyToastType.Success });
+ this.toastService.openMessage('Toast message', { type: SkyToastType.Warning });
+ this.toastService.openMessage('Toast message', { type: SkyToastType.Danger });
+ }
+
+ public openComponents(): void {
+ this.toastService.openComponent(ToastDemoComponent, { type: SkyToastType.Info });
+ this.toastService.openComponent(ToastDemoComponent, { type: SkyToastType.Success });
+ this.toastService.openComponent(ToastDemoComponent, { type: SkyToastType.Warning });
+ this.toastService.openComponent(ToastDemoComponent, { type: SkyToastType.Danger });
}
}
diff --git a/src/demos/toast/toast-demo.component.html b/src/demos/toast/toast-demo.component.html
index 942dbff3f..d631ee3b4 100644
--- a/src/demos/toast/toast-demo.component.html
+++ b/src/demos/toast/toast-demo.component.html
@@ -7,11 +7,11 @@
- {{ type }}
+ {{ type.label }}
diff --git a/src/demos/toast/toast-demo.component.ts b/src/demos/toast/toast-demo.component.ts
index aa7e37c3d..9dea650cb 100644
--- a/src/demos/toast/toast-demo.component.ts
+++ b/src/demos/toast/toast-demo.component.ts
@@ -20,12 +20,24 @@ import {
templateUrl: './toast-demo.component.html'
})
export class SkyToastDemoComponent {
- public selectedType: SkyToastType = 'info';
- public types: SkyToastType[] = [
- 'info',
- 'success',
- 'warning',
- 'danger'
+ public selectedType: SkyToastType = SkyToastType.Info;
+ public types: any[] = [
+ {
+ value: SkyToastType.Info,
+ label: 'Info'
+ },
+ {
+ value: SkyToastType.Success,
+ label: 'Success'
+ },
+ {
+ value: SkyToastType.Warning,
+ label: 'Warning'
+ },
+ {
+ value: SkyToastType.Danger,
+ label: 'Danger'
+ }
];
constructor(
@@ -34,7 +46,7 @@ export class SkyToastDemoComponent {
public openMessage(): void {
const instance = this.toastService.openMessage(
- `This is a ${this.selectedType} toast.`,
+ `This is a sample toast message.`,
{
type: this.selectedType
}
diff --git a/src/modules/toast/toast.component.html b/src/modules/toast/toast.component.html
index 6aa0f501e..48e668921 100644
--- a/src/modules/toast/toast.component.html
+++ b/src/modules/toast/toast.component.html
@@ -4,9 +4,9 @@
>
diff --git a/src/modules/toast/toast.component.spec.ts b/src/modules/toast/toast.component.spec.ts
index ac0b2aeb2..9e1442100 100644
--- a/src/modules/toast/toast.component.spec.ts
+++ b/src/modules/toast/toast.component.spec.ts
@@ -46,21 +46,27 @@ describe('Toast component', () => {
function verifyType(type?: SkyToastType) {
component.toastType = type;
fixture.detectChanges();
- const toastElement = fixture.nativeElement
- .querySelector(`.sky-toast-${toastComponent.toastType}`);
- expect(toastElement).not.toBeNull();
+
+ let className: string;
+ if (SkyToastType[type]) {
+ className = `sky-toast-${SkyToastType[type].toLowerCase()}`;
+ } else {
+ className = `sky-toast-info`;
+ }
+
+ expect(className).toEqual(toastComponent.classNames);
}
it('should set defaults', () => {
- expect(toastComponent.toastType).toEqual('info');
+ expect(toastComponent.toastType).toEqual(SkyToastType.Info);
});
it('should allow setting the toast type', () => {
verifyType(); // default
- verifyType('info');
- verifyType('success');
- verifyType('warning');
- verifyType('danger');
+ verifyType(SkyToastType.Info);
+ verifyType(SkyToastType.Success);
+ verifyType(SkyToastType.Warning);
+ verifyType(SkyToastType.Danger);
});
it('should close the toast when clicking close button', () => {
@@ -72,4 +78,13 @@ describe('Toast component', () => {
expect(toastComponent['isOpen']).toEqual(false);
expect(toastComponent.animationState).toEqual('closed');
});
+
+ it('should set aria attributes', () => {
+ expect(toastComponent.ariaLive).toEqual('polite');
+ expect(toastComponent.ariaRole).toEqual(undefined);
+ fixture.componentInstance.toastType = SkyToastType.Danger;
+ fixture.detectChanges();
+ expect(toastComponent.ariaLive).toEqual('assertive');
+ expect(toastComponent.ariaRole).toEqual('alert');
+ });
});
diff --git a/src/modules/toast/toast.component.ts b/src/modules/toast/toast.component.ts
index c378b7725..2fe3e7022 100644
--- a/src/modules/toast/toast.component.ts
+++ b/src/modules/toast/toast.component.ts
@@ -38,7 +38,7 @@ export class SkyToastComponent implements OnInit {
}
public get toastType(): SkyToastType {
- return this._toastType || 'info';
+ return (this._toastType === undefined) ? SkyToastType.Info : this._toastType;
}
@Output()
@@ -48,6 +48,44 @@ export class SkyToastComponent implements OnInit {
return (this.isOpen) ? 'open' : 'closed';
}
+ public get ariaLive(): string {
+ return (this.toastType === SkyToastType.Danger) ? 'assertive' : 'polite';
+ }
+
+ public get ariaRole(): string {
+ return (this.toastType === SkyToastType.Danger) ? 'alert' : undefined;
+ }
+
+ public get classNames(): string {
+ const classNames: string[] = [];
+
+ let typeLabel: string;
+ switch (this.toastType) {
+ case SkyToastType.Danger:
+ typeLabel = 'danger';
+ break;
+
+ case SkyToastType.Info:
+ default:
+ typeLabel = 'info';
+ break;
+
+ case SkyToastType.Success:
+ typeLabel = 'success';
+ break;
+
+ case SkyToastType.Warning:
+ typeLabel = 'warning';
+ break;
+ }
+
+ classNames.push(
+ `sky-toast-${typeLabel}`
+ );
+
+ return classNames.join(' ');
+ }
+
private isOpen = false;
private _toastType: SkyToastType;
diff --git a/src/modules/toast/toast.service.spec.ts b/src/modules/toast/toast.service.spec.ts
index eae0c5a69..2e72b5df8 100644
--- a/src/modules/toast/toast.service.spec.ts
+++ b/src/modules/toast/toast.service.spec.ts
@@ -17,6 +17,10 @@ import {
SkyToastFixturesModule
} from './fixtures';
+import {
+ SkyToastType
+} from './types';
+
import {
SkyToast
} from './toast';
@@ -93,7 +97,7 @@ describe('Toast service', () => {
describe('openMessage() method', () => {
it('should open a toast with the given message and configuration', function() {
const instance = toastService.openMessage('Real message', {
- type: 'danger'
+ type: SkyToastType.Danger
});
expect(instance).toBeTruthy();
@@ -153,7 +157,7 @@ describe('Toast service', () => {
const instance = toastService.openComponent(
TestComponent,
{
- type: 'danger'
+ type: SkyToastType.Danger
},
[providers]
);
@@ -175,7 +179,7 @@ describe('Toast service', () => {
it('should handle empty providers', () => {
toastService.openComponent(TestComponent, {
- type: 'danger'
+ type: SkyToastType.Danger
});
toastService.toastStream.take(1).subscribe((toasts: SkyToast[]) => {
diff --git a/src/modules/toast/types/toast-type.ts b/src/modules/toast/types/toast-type.ts
index 4a1303800..e5d37167d 100644
--- a/src/modules/toast/types/toast-type.ts
+++ b/src/modules/toast/types/toast-type.ts
@@ -1,3 +1,6 @@
-// TODO: Convert to string enum after upgrading TypeScript to ^2.4.0.
-// https://stackoverflow.com/questions/45191472/typescripts-string-enums-type-is-not-assignable-to-type
-export type SkyToastType = 'danger' | 'info' | 'success' | 'warning';
+export enum SkyToastType {
+ Danger,
+ Info,
+ Success,
+ Warning
+}
From c47b5b230810961a342e741350f2d03975ec9b23 Mon Sep 17 00:00:00 2001
From: Blackbaud-SteveBrush
Date: Mon, 21 May 2018 13:54:07 -0400
Subject: [PATCH 45/46] Close all toasts with animation
---
src/modules/toast/toast.service.spec.ts | 10 +++-------
src/modules/toast/toast.service.ts | 4 +---
src/modules/toast/toaster.component.spec.ts | 16 ++++++++++++++++
src/modules/toast/toaster.component.ts | 13 +++++++++++++
4 files changed, 33 insertions(+), 10 deletions(-)
diff --git a/src/modules/toast/toast.service.spec.ts b/src/modules/toast/toast.service.spec.ts
index 2e72b5df8..e6c2bcf6c 100644
--- a/src/modules/toast/toast.service.spec.ts
+++ b/src/modules/toast/toast.service.spec.ts
@@ -29,10 +29,6 @@ import {
SkyToastAdapterService
} from './toast-adapter.service';
-import {
- SkyToastInstance
-} from './toast-instance';
-
import {
SkyToastService
} from './toast.service';
@@ -88,9 +84,9 @@ describe('Toast service', () => {
});
it('should expose a method to remove the toast from the DOM', () => {
- const instance: SkyToastInstance = toastService.openMessage('message');
- const spy = spyOn(instance, 'close').and.callThrough();
- toastService.ngOnDestroy();
+ toastService.openMessage('message');
+ const spy = spyOn(toastService['host'].instance, 'closeAll').and.callFake(() => {});
+ toastService.closeAll();
expect(spy).toHaveBeenCalledWith();
});
diff --git a/src/modules/toast/toast.service.ts b/src/modules/toast/toast.service.ts
index a4f1b4fab..e081e197b 100644
--- a/src/modules/toast/toast.service.ts
+++ b/src/modules/toast/toast.service.ts
@@ -120,9 +120,7 @@ export class SkyToastService implements OnDestroy {
* Closes all active toast components.
*/
public closeAll(): void {
- this.toasts.forEach(toast => toast.instance.close());
- this.toasts = [];
- this._toastStream.next(this.toasts);
+ this.host.instance.closeAll();
}
private addToast(toast: SkyToast): void {
diff --git a/src/modules/toast/toaster.component.spec.ts b/src/modules/toast/toaster.component.spec.ts
index d76702745..ed4f9b1cb 100644
--- a/src/modules/toast/toaster.component.spec.ts
+++ b/src/modules/toast/toaster.component.spec.ts
@@ -146,4 +146,20 @@ describe('Toast component', () => {
toasts = getToastElements();
expect(toasts.length).toEqual(0);
}));
+
+ it('should close all toasts', fakeAsync(() => {
+ openMessage();
+ openMessage();
+ openMessage();
+
+ let toasts = getToastElements();
+ expect(toasts.length).toEqual(3);
+
+ toastService.closeAll();
+ fixture.detectChanges();
+ tick();
+
+ toasts = getToastElements();
+ expect(toasts.length).toEqual(0);
+ }));
});
diff --git a/src/modules/toast/toaster.component.ts b/src/modules/toast/toaster.component.ts
index d9ee4f97b..9a76d3795 100644
--- a/src/modules/toast/toaster.component.ts
+++ b/src/modules/toast/toaster.component.ts
@@ -23,6 +23,10 @@ import {
SkyToast
} from './toast';
+import {
+ SkyToastComponent
+} from './toast.component';
+
import {
SkyToastAdapterService
} from './toast-adapter.service';
@@ -49,6 +53,9 @@ export class SkyToasterComponent implements AfterViewInit {
@ViewChildren('toastContent', { read: ViewContainerRef })
private toastContent: QueryList;
+ @ViewChildren(SkyToastComponent)
+ private toastComponents: QueryList;
+
constructor(
private domAdapter: SkyToastAdapterService,
private toastService: SkyToastService,
@@ -72,6 +79,12 @@ export class SkyToasterComponent implements AfterViewInit {
toast.instance.close();
}
+ public closeAll(): void {
+ this.toastComponents.forEach((toastComponent) => {
+ toastComponent.close();
+ });
+ }
+
private injectToastContent(): void {
// Dynamically inject each toast's body content when the number of toasts changes.
this.toastService.toastStream.take(1).subscribe((toasts) => {
From 8357564e763d5601ed6d0e66adbef7dbebc76758 Mon Sep 17 00:00:00 2001
From: Blackbaud-SteveBrush
Date: Mon, 21 May 2018 14:03:42 -0400
Subject: [PATCH 46/46] Running ngOnDestroy
---
src/modules/toast/toast.service.spec.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/modules/toast/toast.service.spec.ts b/src/modules/toast/toast.service.spec.ts
index e6c2bcf6c..89581bafd 100644
--- a/src/modules/toast/toast.service.spec.ts
+++ b/src/modules/toast/toast.service.spec.ts
@@ -86,7 +86,7 @@ describe('Toast service', () => {
it('should expose a method to remove the toast from the DOM', () => {
toastService.openMessage('message');
const spy = spyOn(toastService['host'].instance, 'closeAll').and.callFake(() => {});
- toastService.closeAll();
+ toastService.ngOnDestroy();
expect(spy).toHaveBeenCalledWith();
});