123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712 |
- /* global describe global it global beforeEach global angular global jasmine global inject global expect global spyOn */
- 'use strict';
- var rootScope, toaster, $compile;
- describe('toasterContainer', function () {
- beforeEach(function () {
- module('toaster');
-
- // inject the toaster service
- inject(function (_toaster_, _$rootScope_, _$compile_) {
- toaster = _toaster_;
- rootScope = _$rootScope_;
- $compile = _$compile_;
- });
- });
- it('should pop a toast via individual parameters', function () {
- var container = compileContainer();
- var scope = container.scope();
-
- toaster.pop('info', 'test', 'test');
-
- expect(scope.toasters.length).toBe(1);
- });
- it('should unsubscribe events on $destroy if handlers exist', function () {
- var toasterEventRegistry;
- inject(function (_toasterEventRegistry_) {
- toasterEventRegistry = _toasterEventRegistry_;
- });
- var container = compileContainer();
- var scope = container.scope();
- spyOn(toasterEventRegistry, 'unsubscribeToNewToastEvent').and.callThrough();
- spyOn(toasterEventRegistry, 'unsubscribeToClearToastsEvent').and.callThrough();
- scope.$destroy();
- expect(toasterEventRegistry.unsubscribeToNewToastEvent).toHaveBeenCalled();
- expect(toasterEventRegistry.unsubscribeToClearToastsEvent).toHaveBeenCalled();
- });
-
-
- describe('addToast', function () {
- it('should default to icon-class config value if toast.type not found in icon-classes', function () {
- var toasterConfig;
-
- inject(function (_toasterConfig_) {
- toasterConfig = _toasterConfig_;
- });
-
- compileContainer();
-
- expect(toasterConfig['icon-class']).toBe('toast-info');
-
- toaster.pop({ type: 'invalid' });
-
- rootScope.$digest();
-
- expect(toaster.toast.type).toBe('toast-info');
- });
-
- it('should allow subsequent duplicates if prevent-duplicates is not set', function () {
- var container = compileContainer();
- var scope = container.scope();
-
- expect(scope.toasters.length).toBe(0);
-
- toaster.pop({ type: 'info', title: 'title', body: 'body' });
- toaster.pop({ type: 'info', title: 'title', body: 'body' });
-
- rootScope.$digest();
-
- expect(scope.toasters.length).toBe(2);
- });
-
- it('should not allow subsequent duplicates if prevent-duplicates is true without toastId param', function () {
- var container = angular.element(
- '<toaster-container toaster-options="{\'prevent-duplicates\': true}"></toaster-container>');
-
- $compile(container)(rootScope);
- rootScope.$digest();
-
- var scope = container.scope();
-
- expect(scope.toasters.length).toBe(0);
-
- toaster.pop({ type: 'info', title: 'title', body: 'body' });
- toaster.pop({ type: 'info', title: 'title', body: 'body' });
-
- rootScope.$digest();
-
- expect(scope.toasters.length).toBe(1);
- });
-
- it('should allow subsequent duplicates if prevent-duplicates is true with unique toastId params', function () {
- var container = angular.element(
- '<toaster-container toaster-options="{\'prevent-duplicates\': true}"></toaster-container>');
-
- $compile(container)(rootScope);
- rootScope.$digest();
-
- var scope = container.scope();
-
- expect(scope.toasters.length).toBe(0);
-
- toaster.pop({ type: 'info', title: 'title', body: 'body', toastId: 1 });
- toaster.pop({ type: 'info', title: 'title', body: 'body', toastId: 2 });
-
- rootScope.$digest();
-
- expect(scope.toasters.length).toBe(2);
- });
-
- it('should not allow subsequent duplicates if prevent-duplicates is true with identical toastId params', function () {
- var container = angular.element(
- '<toaster-container toaster-options="{\'prevent-duplicates\': true}"></toaster-container>');
-
- $compile(container)(rootScope);
- rootScope.$digest();
-
- var scope = container.scope();
-
- expect(scope.toasters.length).toBe(0);
-
- toaster.pop({ type: 'info', title: 'title', body: 'body', toastId: 1 });
- toaster.pop({ type: 'info', title: 'title', body: 'body', toastId: 1 });
-
- rootScope.$digest();
-
- expect(scope.toasters.length).toBe(1);
- });
-
- it('should not render the close button if showCloseButton is false', function () {
- var container = compileContainer();
-
- toaster.pop({ type: 'info', body: 'With a close button' });
-
- rootScope.$digest();
-
- expect(container.find('button')[0]).toBeUndefined();
- });
-
- it('should use the default close html if toast.closeHtml is undefined', function () {
- var container = compileContainer();
-
- toaster.pop({ type: 'info', body: 'With a close button', showCloseButton: true });
-
- rootScope.$digest();
-
- var buttons = container.find('button');
-
- expect(buttons.length).toBe(1);
- expect(buttons[0].outerHTML).toBe('<button class="toast-close-button" type="button">×</button>');
- });
-
- it('should use the toast.closeHtml argument if passed', function () {
- var container = compileContainer();
-
- toaster.pop({ type: 'info', body: 'With a close button', showCloseButton: true,
- closeHtml: '<button>Close</button>'
- });
-
- rootScope.$digest();
-
- var buttons = container.find('button');
-
- expect(buttons.length).toBe(1);
- expect(buttons[0].outerHTML).toBe('<button>Close</button>');
- });
-
- it('should render toast.closeHtml argument if not a button element', function () {
- var container = compileContainer();
-
- toaster.pop({ type: 'info', body: 'With close text', showCloseButton: true,
- closeHtml: '<span>Close</span>'
- });
-
- rootScope.$digest();
-
- var spans = container.find('span');
-
- expect(spans.length).toBe(1);
- expect(spans[0].outerHTML).toBe('<span>Close</span>');
- });
-
- it('should show the close button if mergedConfig close-button is an object set to true for toast-info', function () {
- var container = angular.element(
- '<toaster-container toaster-options="{\'close-button\': {\'toast-info\': true}}"></toaster-container>');
-
- $compile(container)(rootScope);
- rootScope.$digest();
-
- toaster.pop({ type: 'info' });
-
- rootScope.$digest();
-
- var buttons = container.find('button');
-
- expect(buttons.length).toBe(1);
- expect(buttons[0].outerHTML).toBe('<button class="toast-close-button" type="button">×</button>');
- });
-
- it('should not render the close button if mergedConfig close-button type cannot be found', function () {
- var container = angular.element(
- '<toaster-container toaster-options="{\'close-button\': {\'toast-invalid\': true}}"></toaster-container>');
-
- $compile(container)(rootScope);
- rootScope.$digest();
-
- toaster.pop({ type: 'info' });
-
- rootScope.$digest();
-
- var buttons = container.find('button');
-
- expect(buttons.length).toBe(0);
- expect(buttons[0]).toBeUndefined();
- });
-
- it('should not render the close button if mergedConfig close-button is not an object', function () {
- var container = angular.element(
- '<toaster-container toaster-options="{\'close-button\': 1 }"></toaster-container>');
-
- $compile(container)(rootScope);
- rootScope.$digest();
-
- toaster.pop({ type: 'info' });
-
- rootScope.$digest();
-
- var buttons = container.find('button');
-
- expect(buttons.length).toBe(0);
- expect(buttons[0]).toBeUndefined();
- });
-
- it('should render trustedHtml bodyOutputType', function () {
- var container = compileContainer();
-
- toaster.pop({ bodyOutputType: 'trustedHtml', body: '<section>Body</section>' });
-
- rootScope.$digest();
-
- var body = container.find('section');
-
- expect(body.length).toBe(1);
- expect(body[0].outerHTML).toBe('<section>Body</section>');
- });
-
- it('should render template bodyOutputType when body is passed', function () {
- inject(function($templateCache) {
- $templateCache.put('/templatepath/template.html', '<section>Template</section>');
- });
-
- var container = compileContainer();
-
- toaster.pop({ bodyOutputType: 'template', body: '/templatepath/template.html' });
-
- rootScope.$digest();
-
- expect(toaster.toast.body).toBe('/templatepath/template.html');
-
- var body = container.find('section');
-
- expect(body.length).toBe(1);
- expect(body[0].outerHTML).toBe('<section class="ng-scope">Template</section>');
- });
-
- it('should render default template bodyOutputType when body is not passed', function () {
- inject(function($templateCache) {
- $templateCache.put('toasterBodyTmpl.html', '<section>Template</section>');
- });
-
- var container = compileContainer();
-
- toaster.pop({ bodyOutputType: 'template' });
-
- rootScope.$digest();
-
- expect(toaster.toast.bodyTemplate).toBe('toasterBodyTmpl.html');
-
- var body = container.find('section');
-
- expect(body.length).toBe(1);
- expect(body[0].outerHTML).toBe('<section class="ng-scope">Template</section>');
- });
-
- it('should render templateWithData bodyOutputType when body is passed', function () {
- inject(function($templateCache) {
- $templateCache.put('template.html', '<section>Template {{toaster.data}}</section>');
- });
-
- var container = compileContainer();
-
- toaster.pop({ bodyOutputType: 'templateWithData', body: "{template: 'template.html', data: 123 }" });
-
- rootScope.$digest();
-
- var body = container.find('section');
-
- expect(body.length).toBe(1);
- expect(body[0].outerHTML).toBe('<section class="ng-binding ng-scope">Template 123</section>');
- });
-
- it('should throw exception for default templateWithData bodyOutputType when body is not passed', function () {
- // TODO: If the default fallback template cannot be parsed to an object
- // composed of template and data, an exception is thrown. This seems to
- // be undesirable behavior. A clearer exception should be thrown, or better
- // handling should be handled, or the fallback option should be removed.
- inject(function($templateCache) {
- $templateCache.put('template.html', '<section>Template {{toaster.data}}</section>');
- });
-
- compileContainer();
- var hasException = false;
-
- try {
- toaster.pop({ bodyOutputType: 'templateWithData' });
- } catch (e) {
- expect(e.message).toBe("Cannot read property 'template' of undefined");
- hasException = true;
- }
-
- expect(hasException).toBe(true);
- });
-
- it('should remove first in toast if limit is met and newest-on-top is true', function () {
- var container = angular.element(
- '<toaster-container toaster-options="{\'limit\': 2, \'newest-on-top\': true }"></toaster-container>');
-
- $compile(container)(rootScope);
- rootScope.$digest();
-
- var scope = container.scope();
-
- toaster.pop({ type: 'info', body: 'first' });
- toaster.pop({ type: 'info', body: 'second' });
-
- rootScope.$digest();
-
- expect(scope.toasters.length).toBe(2);
- expect(scope.toasters[0].body).toBe('second');
- expect(scope.toasters[1].body).toBe('first');
-
- toaster.pop({ type: 'info', body: 'third' });
-
- rootScope.$digest();
-
- expect(scope.toasters.length).toBe(2);
- expect(scope.toasters[0].body).toBe('third');
- expect(scope.toasters[1].body).toBe('second');
- });
-
- it('should remove last in toast if limit is met and newest-on-top is false', function () {
- var container = angular.element(
- '<toaster-container toaster-options="{\'limit\': 2, \'newest-on-top\': false }"></toaster-container>');
-
- $compile(container)(rootScope);
- rootScope.$digest();
-
- var scope = container.scope();
-
- toaster.pop({ type: 'info', body: 'first' });
- toaster.pop({ type: 'info', body: 'second' });
-
- rootScope.$digest();
-
- expect(scope.toasters.length).toBe(2);
- expect(scope.toasters[0].body).toBe('first');
- expect(scope.toasters[1].body).toBe('second');
-
- toaster.pop({ type: 'info', body: 'third' });
-
- rootScope.$digest();
-
- expect(scope.toasters.length).toBe(2);
- expect(scope.toasters[0].body).toBe('second');
- expect(scope.toasters[1].body).toBe('third');
- });
- });
-
-
- describe('removeToast', function () {
- it('should not remove toast if id does not match a toast id', function() {
- var container = compileContainer();
- var scope = container.scope();
-
- toaster.pop({ type: 'info', body: 'toast 1' });
- toaster.pop({ type: 'info', body: 'toast 2' });
-
- rootScope.$digest();
-
- expect(scope.toasters.length).toBe(2);
- expect(scope.toasters[0].id).toBe(2)
- expect(scope.toasters[1].id).toBe(1)
-
- scope.removeToast(3);
-
- rootScope.$digest();
-
- expect(scope.toasters.length).toBe(2);
- });
-
- it('should invoke onHideCallback if it exists when toast is removed', function () {
- var container = compileContainer();
- var scope = container.scope();
-
- var mock = {
- callback : function () { }
- };
-
- spyOn(mock, 'callback');
-
- toaster.pop({ type: 'info', body: 'toast 1', onHideCallback: mock.callback });
-
- rootScope.$digest();
- scope.removeToast(1);
- rootScope.$digest();
-
- expect(mock.callback).toHaveBeenCalled();
- });
- });
- describe('scope._onNewTest', function () {
- it('should not add toast if toasterId is passed to scope._onNewToast but toasterId is not set via config', function () {
- var container = compileContainer();
- var scope = container.scope();
-
- expect(scope.config.toasterId).toBeUndefined();
-
- toaster.pop({ type: 'info', body: 'toast 1', toasterId: 1 });
-
- rootScope.$digest();
-
- expect(scope.toasters.length).toBe(0);
- });
-
- it('should add toast if toasterId is passed to scope._onNewToast and toasterId is set via config', function () {
- var container = angular.element(
- '<toaster-container toaster-options="{ \'toaster-id\': 1 }"></toaster-container>');
-
- $compile(container)(rootScope);
- rootScope.$digest();
- var scope = container.scope();
-
- expect(scope.config.toasterId).toBe(1);
-
- toaster.pop({ type: 'info', body: 'toast 1', toasterId: 1 });
-
- rootScope.$digest();
-
- expect(scope.toasters.length).toBe(1);
- });
-
- it('should add toasts to their respective container based on toasterId', function () {
- var container1 = angular.element(
- '<toaster-container toaster-options="{ \'toaster-id\': 1 }"></toaster-container>');
- var container2 = angular.element(
- '<toaster-container toaster-options="{ \'toaster-id\': 2 }"></toaster-container>');
-
- $compile(container1)(rootScope);
- $compile(container2)(rootScope);
- rootScope.$digest();
-
- var scope1 = container1.scope();
- var scope2 = container2.scope();
-
- toaster.pop({ type: 'info', body: 'toast 1', toasterId: 1 });
- toaster.pop({ type: 'info', body: 'toast 2', toasterId: 2 });
-
- rootScope.$digest();
-
- expect(scope1.toasters.length).toBe(1);
- expect(scope2.toasters.length).toBe(1);
- });
- });
- describe('scope._onClearToasts', function (){
- it('should remove all toasts from all containers if toasterId is *', function () {
- var container1 = angular.element(
- '<toaster-container toaster-options="{ \'toaster-id\': 1 }"></toaster-container>');
- var container2 = angular.element(
- '<toaster-container toaster-options="{ \'toaster-id\': 2 }"></toaster-container>');
-
- $compile(container1)(rootScope);
- $compile(container2)(rootScope);
- rootScope.$digest();
-
- var scope1 = container1.scope();
- var scope2 = container2.scope();
-
- toaster.pop({ type: 'info', body: 'toast 1', toasterId: 1 });
- toaster.pop({ type: 'info', body: 'toast 2', toasterId: 2 });
-
- rootScope.$digest();
-
- expect(scope1.toasters.length).toBe(1);
- expect(scope2.toasters.length).toBe(1);
-
- toaster.clear('*');
-
- rootScope.$digest();
-
- expect(scope1.toasters.length).toBe(0);
- expect(scope2.toasters.length).toBe(0);
- });
-
- it('should remove all toasts from all containers if config.toasterId and toastId are undefined', function () {
- var container1 = angular.element(
- '<toaster-container toaster-options="{ \'close-button\': false }"></toaster-container>');
- var container2 = angular.element(
- '<toaster-container toaster-options="{ \'close-button\': true }" ></toaster-container>');
-
- $compile(container1)(rootScope);
- $compile(container2)(rootScope);
- rootScope.$digest();
-
- var scope1 = container1.scope();
- var scope2 = container2.scope();
-
- toaster.pop({ type: 'info', body: 'toast 1' });
- toaster.pop({ type: 'info', body: 'toast 2' });
-
- rootScope.$digest();
-
- // since there are two separate instances of the container
- // without a toasterId, both receive the newToast event
- expect(scope1.toasters.length).toBe(2);
- expect(scope2.toasters.length).toBe(2);
-
- toaster.clear();
-
- rootScope.$digest();
-
- expect(scope1.toasters.length).toBe(0);
- expect(scope2.toasters.length).toBe(0);
- });
-
- it('should not remove by toasterId / toastId from the correct container if toast.toasterId is defined and toast.toastId is undefined', function () {
- var container1 = angular.element(
- '<toaster-container toaster-options="{ \'toaster-id\': 1 }"></toaster-container>');
- var container2 = angular.element(
- '<toaster-container toaster-options="{ \'toaster-id\': 2 }"></toaster-container>');
-
- $compile(container1)(rootScope);
- $compile(container2)(rootScope);
- rootScope.$digest();
-
- var scope1 = container1.scope();
- var scope2 = container2.scope();
-
- // removeAllToasts explicitly looks for toast.uid, which is only set
- // if toastId is passed as a parameter
- toaster.pop({ type: 'info', body: 'toast 1', toasterId: 1 });
- toaster.pop({ type: 'info', body: 'toast 2', toasterId: 2 });
- toaster.pop({ type: 'info', body: 'toast 3', toasterId: 2 });
-
- rootScope.$digest();
-
- expect(scope1.toasters.length).toBe(1);
- expect(scope2.toasters.length).toBe(2);
-
- toaster.clear(2, 1);
-
- rootScope.$digest();
-
- expect(scope1.toasters.length).toBe(1);
- expect(scope2.toasters.length).toBe(2);
- });
-
- it('should remove by toasterId / toastId from the correct container if toasterId is defined and toastId is defined', function () {
- var container1 = angular.element(
- '<toaster-container toaster-options="{ \'toaster-id\': 1 }"></toaster-container>');
- var container2 = angular.element(
- '<toaster-container toaster-options="{ \'toaster-id\': 2 }"></toaster-container>');
-
- $compile(container1)(rootScope);
- $compile(container2)(rootScope);
- rootScope.$digest();
-
- var scope1 = container1.scope();
- var scope2 = container2.scope();
-
- // removeAllToasts explicitly looks for toast.uid, which is only set
- // if toastId is passed as a parameter
- toaster.pop({ type: 'info', body: 'toast 1', toasterId: 1, toastId: 1 });
- toaster.pop({ type: 'info', body: 'toast 2', toasterId: 2, toastId: 1 });
- toaster.pop({ type: 'info', body: 'toast 3', toasterId: 2, toastId: 2 });
-
- rootScope.$digest();
-
- expect(scope1.toasters.length).toBe(1);
- expect(scope2.toasters.length).toBe(2);
-
- toaster.clear(2, 1);
-
- rootScope.$digest();
-
- expect(scope1.toasters.length).toBe(1);
- expect(scope2.toasters.length).toBe(1);
- });
- });
- });
- describe('toasterContainer', function () {
- var $interval, $intervalSpy;
- inject(function (_$interval_) {
- $interval = _$interval_;
- });
- beforeEach(function () {
- $intervalSpy = jasmine.createSpy('$interval', $interval);
- module('toaster', function ($provide) {
- $provide.value('$interval', $intervalSpy);
- });
- // inject the toaster service
- inject(function (_toaster_, _$rootScope_, _$compile_) {
- toaster = _toaster_;
- rootScope = _$rootScope_;
- $compile = _$compile_;
- });
- });
- it('should use the toast.timeout argument if it is a valid number', function () {
- var container = compileContainer();
- var scope = container.scope();
- spyOn(scope, 'configureTimer').and.callThrough();
- toaster.pop({ timeout: 2 });
- expect(scope.configureTimer).toHaveBeenCalled();
- expect(scope.configureTimer.calls.allArgs()[0][0].timeout).toBe(2);
- expect($intervalSpy.calls.first().args[1]).toBe(2)
- });
- it('should not use the toast.timeout argument if not a valid number', function () {
- var container = compileContainer();
- var scope = container.scope();
- spyOn(scope, 'configureTimer').and.callThrough();
- toaster.pop({ timeout: "2" });
- expect(scope.configureTimer).toHaveBeenCalled();
- expect(scope.configureTimer.calls.allArgs()[0][0].timeout).toBe("2");
- expect($intervalSpy.calls.first().args[1]).toBe(5000);
- });
- it('should call scope.removeToast when toast.timeoutPromise expires', function () {
- var container = compileContainer();
- var scope = container.scope();
- spyOn(scope, 'removeToast').and.callThrough();
- toaster.pop({ timeout: 2 });
- $intervalSpy.calls.first().args[0]();
- rootScope.$digest();
- expect(scope.removeToast).toHaveBeenCalled();
- });
- it('should retrieve timeout by toast type if mergedConfig toast-timeout is an object', function () {
- var element = angular.element(
- '<toaster-container toaster-options="{\'time-out\': {\'toast-info\': 5}}"></toaster-container>');
- $compile(element)(rootScope);
- rootScope.$digest();
- toaster.pop({ type: 'info' });
- expect($intervalSpy.calls.first().args[1]).toBe(5);
- });
- it('should not set a timeout if mergedConfig toast-timeout is an object and does not match toast type', function () {
- // TODO: this seems to be a bug in the toast-timeout configuration option.
- // It should fall back to a default value if the toast type configuration
- // does not match the target toast type or throw an exception to warn of an
- // invalid configuration.
-
- var element = angular.element(
- '<toaster-container toaster-options="{\'time-out\': {\'toast-info\': 5}}"></toaster-container>');
- $compile(element)(rootScope);
- rootScope.$digest();
- toaster.pop({ type: 'warning' });
- expect($intervalSpy.calls.all().length).toBe(0);
- });
- });
- function compileContainer() {
- var element = angular.element('<toaster-container></toaster-container>');
- $compile(element)(rootScope);
- rootScope.$digest();
- return element;
- }
|