Skip to content

Commit

Permalink
Merge pull request #218 from Stabzs/feat/progress_bar
Browse files Browse the repository at this point in the history
feat(progress bar)
  • Loading branch information
Stabzs authored Nov 19, 2020
2 parents b815a3e + 6320640 commit 530dc32
Show file tree
Hide file tree
Showing 15 changed files with 626 additions and 382 deletions.
26 changes: 25 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,34 @@ containers can exist on the same page, using a class for controlling container s
appropriate.
* **toaster.css:** IE support has been dropped and the styles have been ported to a much lighter-weight flexbox
pattern. This will most likely break any custom toast styles.
* **toaster.css:** The Toaster Container element and styles have been moved from an id to a class. This allows for
custom ids to be applied to individual containers and more semantically supports multiple containers on the same page.
* **toast, toaster.service, toaster-config:** the 'type' parameter has been constrained to the new `ToastType` type
instead of string. If the default types are being used, there will be no impact. If custom types are being used,
the custom types will need a new type that unions `ToastType`. See the 'Toast Types' section of the README for
additional details.
- To more clearly support this change, the `toaster-config.defaultTypeClass` property has been
renamed to `defaultToastType` and is now constrained to `ToastType`.
- The following toaster-config properties have been updated from type `string` to type `ToastType`:
- showCloseButton
- typeClasses
- iconClasses
- defaultToastType
- timeout
* **toaster-container.component:** When providing a custom toast type, overrides must be added via `toaster-config` to
both the `typeClasses` and `iconClasses` properties. If mappings do not exist for both, the toast's type will fall back
to `toaster-config.defaultToastType`.

### BUG FIXES
* **toaster-config:** The default for the `defaultTypeClass` property (now renamed to `defaultToastType`) has been
correctly changed to `info` instead of `toast-info` for the fallback case.
* **toaster.css:** The close button positioning has been corrected.
Closes [#192](https://github.com/Stabzs/Angular2-Toaster/issues/192).


# 10.0.0
### FEATURES
* **angular2-toaster:** Full release of 10.0.0 functionality. Pins the llibrary to 10.0.0 of Angular.
* **angular2-toaster:** Full release of 10.0.0 functionality. Pins the library to 10.0.0 of Angular.

### DOCUMENTATION
* **README:** Added documentation for toast types and toast type overrides.
Expand Down
331 changes: 331 additions & 0 deletions src/angular2-toaster/src/lib/toast.component.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,331 @@
import { ComponentFixture, fakeAsync, flush, TestBed, tick } from '@angular/core/testing';
import { ToastComponent } from "./toast.component"
import { ToasterConfig } from './toaster-config';
import { Toast, ToastType } from './toast';

type ExtendedToastType = ('customtype' | '') & ToastType;

describe('ToastComponent', () => {
let fixture : ComponentFixture<ToastComponent>;
let component: ToastComponent;

beforeEach(() => {
TestBed.configureTestingModule({
declarations: [ ToastComponent ]
});
fixture = TestBed.createComponent(ToastComponent);
component = fixture.componentInstance;
});

describe('ngOnInit', () => {
it('should map toasterconfig.timeout object if defined and type exists', () => {
component.toasterconfig = new ToasterConfig({ timeout: { 'info': 10.1 } });
component.toast = <Toast> { type: 'info', title: 'test', body: 'test' };
component.ngOnInit();

expect(component['timeout']).toBe(10.1)
});

it('should map toasterconfig.timeout object to undefined if defined and type does not exist', () => {
component.toasterconfig = new ToasterConfig({ timeout: { 'info': 10.1 } });
component.toast = <Toast> { type: <ExtendedToastType>'custom', title: 'test', body: 'test' };
component.ngOnInit();

expect(component['timeout']).toBeUndefined();
});
});

describe('mouseenter event', () => {
it('should clear timer if mouseOverTimerStop is true', fakeAsync(() => {
component.toasterconfig = new ToasterConfig({ mouseoverTimerStop: true, timeout: 100 });
component.toast = <Toast> { type: 'success', title: 'test', body: 'test' };
component.ngOnInit();
component.ngAfterViewInit();

expect(component['timeoutId']).toBeGreaterThan(0);

fixture.debugElement.triggerEventHandler('mouseenter', {});
tick(0);

expect(component['timeoutId']).toBeNull();
expect(flush()).toBe(0);
}));

it('should reset progressBarWidth if mouseOverTimerStop is true', fakeAsync(() => {
component.toasterconfig = new ToasterConfig({ mouseoverTimerStop: true, timeout: 100});
component.toast = <Toast> { type: 'success', title: 'test', body: 'test', progressBar: true };
component.ngOnInit();
component.ngAfterViewInit();

// tick for 10 milliseconds (first interval) to allow progressBar to set width
tick(10);

expect(component['timeoutId']).toBeGreaterThan(0);
expect(component['progressBarWidth']).toBeGreaterThan(0);

fixture.debugElement.triggerEventHandler('mouseenter', {});
tick(0);

expect(component['progressBarIntervalId']).toBeNull();
expect(component['progressBarWidth']).toBe(0);
expect(flush()).toBe(0);
}));

it('should not clear timer if mouseOverTimerStop is false', fakeAsync(() => {
component.toasterconfig = new ToasterConfig({ mouseoverTimerStop: false, timeout: 100 });
component.toast = <Toast> { type: 'success', title: 'test', body: 'test' };
component.ngOnInit();
component.ngAfterViewInit();

expect(component['timeoutId']).toBeGreaterThan(0);

fixture.debugElement.triggerEventHandler('mouseenter', {});
tick(0);

expect(component['timeoutId']).toBeGreaterThan(0);
expect(flush()).toBe(100);
}));

it('should not clear timer if if mouseOverTimerStop is true and timeout is 0', fakeAsync(() => {
component.toasterconfig = new ToasterConfig({ mouseoverTimerStop: false, timeout: 0 });
component.toast = <Toast> { type: 'success', title: 'test', body: 'test' };
component.ngOnInit();
component.ngAfterViewInit();

expect(component['timeoutId']).toBeNull();

spyOn(window, 'clearTimeout');

fixture.debugElement.triggerEventHandler('mouseenter', {});
tick(0);

expect(window.clearTimeout).not.toHaveBeenCalled();
expect(component['timeoutId']).toBeNull();
expect(flush()).toBe(0);
}));
});

describe('mouseleave event', () => {
it('should restart timer if mouseOverTimerStop is true and timeoutId not defined', fakeAsync(() => {
component.toasterconfig = new ToasterConfig({ mouseoverTimerStop: true, timeout: 100 });
component.toast = <Toast> { type: 'success', title: 'test', body: 'test' };
component.ngOnInit();
component.ngAfterViewInit();

expect(component['timeoutId']).toBeGreaterThan(0);

component.stopTimer();

expect(component['timeoutId']).toBeNull();

spyOn<any>(component, 'configureTimer').and.callThrough();
spyOn<any>(component, 'removeToast').and.callThrough();

fixture.debugElement.triggerEventHandler('mouseleave', {});

tick(0);

expect(component['timeoutId']).toBeGreaterThan(0);

expect(component['configureTimer']).toHaveBeenCalled();
expect(component['removeToast']).not.toHaveBeenCalled();
expect(component['timeoutId']).toBeGreaterThan(0);
expect(flush()).toBe(100);
}));

it('should not restart timer if mouseOverTimerStop is true and timeoutId is defined', fakeAsync(() => {
component.toasterconfig = new ToasterConfig({ mouseoverTimerStop: true, timeout: 100 });
component.toast = <Toast> { type: 'success', title: 'test', body: 'test' };
component.ngOnInit();
component.ngAfterViewInit();

expect(component['timeoutId']).toBeGreaterThan(0);

spyOn<any>(component, 'configureTimer').and.callThrough();
spyOn(window, 'setTimeout');

tick(50);

fixture.debugElement.triggerEventHandler('mouseleave', {});

tick(0);

expect(component['configureTimer']).not.toHaveBeenCalled();
expect(window.setTimeout).not.toHaveBeenCalled();
expect(component['timeoutId']).toBeDefined();

// the timeer did not restart and the remainder needs to be flushed
expect(flush()).toBe(50);
}));

it('should not restart timer if mouseOverTimerStop is false', fakeAsync(() => {
component.toasterconfig = new ToasterConfig({ mouseoverTimerStop: false, timeout: 100 });
component.toast = <Toast> { type: 'success', title: 'test', body: 'test' };
component.ngOnInit();
component.ngAfterViewInit();

expect(component['timeoutId']).toBeGreaterThan(0);

spyOn<any>(component, 'configureTimer').and.callThrough();
spyOn(window, 'setTimeout');

tick(50);
expect(component['timeoutId']).toBeGreaterThan(0);

fixture.debugElement.triggerEventHandler('mouseleave', {});

tick(0);

expect(component['configureTimer']).not.toHaveBeenCalled();
expect(window.setTimeout).not.toHaveBeenCalled();
expect(component['timeoutId']).toBeGreaterThan(0);
expect(flush()).toBe(50);
}));

it('should not restart timer if mouseOverTimerStop is false and toast.timeout is 0', fakeAsync(() => {
component.toasterconfig = new ToasterConfig({ mouseoverTimerStop: false });
component.toast = <Toast> { type: 'success', title: 'test', body: 'test', timeout: 0 };
component.ngOnInit();
component.ngAfterViewInit();

expect(component['timeoutId']).toBeNull();

spyOn<any>(component, 'configureTimer').and.callThrough();
spyOn(window, 'setTimeout');

fixture.debugElement.triggerEventHandler('mouseleave', {});

tick(0);

expect(component['configureTimer']).not.toHaveBeenCalled();
expect(window.setTimeout).not.toHaveBeenCalled();
expect(component['timeoutId']).toBeNull();
expect(flush()).toBe(0);
}));

it('should remove toast if mouseOverTimerStop is false and timeoutId is null and timeout has value', fakeAsync(() => {
component.toasterconfig = new ToasterConfig({ mouseoverTimerStop: false, timeout: 100 });
component.toast = <Toast> { type: 'success', title: 'test', body: 'test' };
component.ngOnInit();
component.ngAfterViewInit();

expect(component['timeoutId']).toBeGreaterThan(0);

spyOn<any>(component, 'configureTimer').and.callThrough();
spyOn<any>(component, 'removeToast').and.callThrough();

component['clearTimers']();

expect(component['timeoutId']).toBeNull();
expect(component['removeToast']).not.toHaveBeenCalled();

fixture.debugElement.triggerEventHandler('mouseleave', {});

tick(0);

expect(component['timeoutId']).toBeNull();
expect(component['configureTimer']).not.toHaveBeenCalled();
expect(component['removeToast']).toHaveBeenCalled();
}));

it('should not remove toast if mouseOverTimerStop is false and timeoutId is null and toast is sticky', fakeAsync(() => {
component.toasterconfig = new ToasterConfig({ mouseoverTimerStop: false, timeout: 0 });
component.toast = <Toast> { type: 'success', title: 'test', body: 'test' };
component.ngOnInit();
component.ngAfterViewInit();

expect(component['timeoutId']).toBeNull();

spyOn<any>(component, 'configureTimer').and.callThrough();
spyOn<any>(component, 'removeToast').and.callThrough();

fixture.debugElement.triggerEventHandler('mouseleave', {});

tick(0);

expect(component['timeoutId']).toBeNull();
expect(component['configureTimer']).not.toHaveBeenCalled();
expect(component['removeToast']).not.toHaveBeenCalled();
}));
})

describe('updateProgressBar', () => {
it('should return if progressBarWidth is 0', () => {
component.toasterconfig = new ToasterConfig({ mouseoverTimerStop: false, timeout: 0 });
component.toast = <Toast> { type: 'success', title: 'test', body: 'test' };
component.ngOnInit();
component.ngAfterViewInit();

component.progressBarWidth = 0;

component['updateProgressBar']();

expect(component.progressBarWidth).toBe(0);
});

it('should return if progressBarWidth is 100', () => {
component.toasterconfig = new ToasterConfig({ mouseoverTimerStop: false, timeout: 0 });
component.toast = <Toast> { type: 'success', title: 'test', body: 'test' };
component.ngOnInit();
component.ngAfterViewInit();

component.progressBarWidth = 100;

component['updateProgressBar']();

expect(component.progressBarWidth).toBe(100);
});

it('should update progressBarWidth if in between 0 and 100', () => {
component.toasterconfig = new ToasterConfig({ mouseoverTimerStop: false, timeout: 100 });
component.toast = <Toast> { type: 'success', title: 'test', body: 'test' };
component.ngOnInit();
component.ngAfterViewInit();

component.progressBarWidth = 25;
component['removeToastTick'] = new Date().getTime() + 75;

component['updateProgressBar']();
expect(component.progressBarWidth).toBe(75);
});

it('should invert progressBarWidth if progressBarDirection is increasing', () => {
component.toasterconfig = new ToasterConfig({ mouseoverTimerStop: false, timeout: 100 });
component.toast = <Toast> { type: 'success', title: 'test', body: 'test', progressBarDirection: 'increasing' };
component.ngOnInit();
component.ngAfterViewInit();

component.progressBarWidth = 25;
component['removeToastTick'] = new Date().getTime() + 75;

component['updateProgressBar']();
expect(component.progressBarWidth).toBe(25);
});

it('should set progressBarWidth to 0 if offset is less than 0', () => {
component.toasterconfig = new ToasterConfig({ mouseoverTimerStop: false, timeout: 100 });
component.toast = <Toast> { type: 'success', title: 'test', body: 'test' };
component.ngOnInit();
component.ngAfterViewInit();

component.progressBarWidth = 25;
component['removeToastTick'] = new Date().getTime() - 75;

component['updateProgressBar']();
expect(component.progressBarWidth).toBe(0);
});

it('should set progressBarWidth to 100 if offset is greater than 100', () => {
component.toasterconfig = new ToasterConfig({ mouseoverTimerStop: false, timeout: 100 });
component.toast = <Toast> { type: 'success', title: 'test', body: 'test' };
component.ngOnInit();
component.ngAfterViewInit();

component.progressBarWidth = 75;
component['removeToastTick'] = new Date().getTime() + 101;

component['updateProgressBar']();
expect(component.progressBarWidth).toBe(100);
});
})
});
Loading

0 comments on commit 530dc32

Please sign in to comment.