Skip to content

Commit

Permalink
✨ notification service (#736) (#745)
Browse files Browse the repository at this point in the history
* 383 notification service (#736)

* feat: add home icon

* feat: add new icon #594

* refactor: replacing config2 icon #594

* feat: #383 creating notification service

* feat: #383 ajusting width

* feat: #383 updating public-api

* feat: inserting notification service in storybooks and separating the notification component

* fix: lint

---------

Co-authored-by: Lucas <[email protected]>
Co-authored-by: Alysson Keysson <[email protected]>
Co-authored-by: Alysson Keysson <[email protected]>

* Delete yarn.lock

* Revert "Delete yarn.lock"

This reverts commit 432faba.

* chore: remove trim of yarn.lock

* fix: fixing tests

* refactor: updating notification service ts and spec #383

* refactor: changes pointed on review #383

* refactor: adding test to complete coverage #383

* fix: lint fix

* refactor: review changes, refactoring ts file and new test case #383

---------

Co-authored-by: lucasrasec <[email protected]>
Co-authored-by: Lucas <[email protected]>
Co-authored-by: Alysson Keysson <[email protected]>
Co-authored-by: Iury Nogueira <[email protected]>
Co-authored-by: Lucas <[email protected]>
Co-authored-by: lucas-alcantara-brisa <[email protected]>
  • Loading branch information
7 people authored Sep 19, 2023
1 parent 7becdc3 commit c8b43d1
Show file tree
Hide file tree
Showing 14 changed files with 546 additions and 22 deletions.
5 changes: 4 additions & 1 deletion projects/ion/src/lib/core/types/notification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@ import { fadeInDirection, fadeOutDirection } from '../../utils/animationsTypes';
import { IconType } from './icon';
import { StatusType } from './status';

export interface NotificationProps {
export interface NotificationProps extends NotificationConfigOptions {
title: string;
message: string;
type?: StatusType;
}

export interface NotificationConfigOptions {
icon?: IconType;
fixed?: boolean;
fadeIn?: fadeInDirection;
Expand Down
2 changes: 1 addition & 1 deletion projects/ion/src/lib/icon/mock/list-icons.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { CommonModule } from '@angular/common';
import { IonIconComponent } from '../icon.component';
import { IonInputComponent } from '../../input/input.component';
import { FormsModule } from '@angular/forms';
import { IonNotificationComponent } from '../../notification/notification.component';
import { IonNotificationComponent } from '../../notification/component/notification.component';

@NgModule({
declarations: [IonInputComponent, IonIconComponent, IonNotificationComponent],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
@import '../../styles/index.scss';
@import '../utils/fadeAnimations.scss';
@import '../../../styles/index.scss';
@import '../../utils/fadeAnimations.scss';

@mixin icon-color($color) {
::ng-deep svg {
Expand All @@ -13,9 +13,11 @@
align-items: flex-start;
justify-content: space-between;
padding: spacing(1.5) spacing(2);

position: relative;
z-index: $zIndexMax;
max-width: 500px;
min-width: 250px;

background: rgba(255, 255, 255, 0.9);
box-shadow: 0px 8px 6px -4px rgba(0, 0, 0, 0.15),
Expand Down Expand Up @@ -75,3 +77,11 @@
.negative-icon {
@include icon-color($negative-color);
}

.notification-container:nth-child(2) {
top: 30px;
}

.notification-container:nth-child(3) {
top: 30px;
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { EventEmitter } from '@angular/core';
import { fireEvent, render, screen } from '@testing-library/angular';
import { StatusType } from '../core/types';
import { NotificationProps } from '../core/types/notification';
import { IonIconModule } from '../icon/icon.module';
import { StatusType } from '../../core/types';
import { NotificationProps } from '../../core/types/notification';
import { IonIconModule } from '../../icon/icon.module';
import { IonNotificationComponent } from './notification.component';

const defaultNotification = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ import {
ViewChild,
} from '@angular/core';
import { Subscription } from 'rxjs';
import { IconType } from '../core/types/icon';
import { NotificationProps } from '../core/types/notification';
import { setTimer } from '../utils/setTimer';
import { IconType } from '../../core/types/icon';
import { NotificationProps } from '../../core/types/notification';
import { setTimer } from '../../utils/setTimer';

@Component({
selector: 'ion-notification',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { IonNotificationService } from './../service/notification.service';
import { Component } from '@angular/core';

@Component({
selector: 'open-notification-button',
template: `
<div style="display: flex; flex-direction: column; gap: 2rem;">
<ion-button
class="button"
label="success"
iconType="check"
(ionOnClick)="notificationSuccess()"
></ion-button>
<ion-button
class="button"
label="warning"
type="dashed"
iconType="exclamation"
(ionOnClick)="notificationWarning()"
></ion-button>
<ion-button
class="button"
label="info"
type="secondary"
iconType="info"
(ionOnClick)="notificationInfo()"
></ion-button>
<ion-button
class="button"
label="error"
[danger]="true"
iconType="close"
(ionOnClick)="notificationError()"
></ion-button>
<div></div>
</div>
`,
styles: [
`
.button {
/deep/ button {
width: 150px !important;
}
}
`,
],
})
export class OpenNotificationButtonComponent {
constructor(private ionNotificationService: IonNotificationService) {}

notificationSuccess(): void {
this.ionNotificationService.success(
'Title...',
'Lorem ipsum dolor sit amet, consectetur adipiscing elit...'
);
}

notificationWarning(): void {
this.ionNotificationService.warning(
'Title...',
'Lorem ipsum dolor sit amet, consectetur adipiscing elit...'
);
}

notificationInfo(): void {
this.ionNotificationService.info(
'Title...',
'Lorem ipsum dolor sit amet, consectetur adipiscing elit...'
);
}

notificationError(): void {
this.ionNotificationService.error(
'Title...',
'Lorem ipsum dolor sit amet, consectetur adipiscing elit...'
);
}
}
13 changes: 10 additions & 3 deletions projects/ion/src/lib/notification/notification.module.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
import { IonNotificationContainerComponent } from './service/notification.container.component';
import { IonNotificationService } from './service/notification.service';
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { IonIconModule } from '../icon/icon.module';
import { IonNotificationComponent } from './notification.component';
import { IonNotificationComponent } from './component/notification.component';

@NgModule({
declarations: [IonNotificationComponent],
declarations: [IonNotificationComponent, IonNotificationContainerComponent],
imports: [CommonModule, IonIconModule],
exports: [IonNotificationComponent],
providers: [IonNotificationService],
exports: [IonNotificationComponent, IonNotificationContainerComponent],
entryComponents: [
IonNotificationComponent,
IonNotificationContainerComponent,
],
})
export class IonNotificationModule {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { IonNotificationComponent } from '../component/notification.component';
import { Component, ComponentRef, Renderer2, ElementRef } from '@angular/core';

@Component({
selector: 'notification-container',
template: '',
styleUrls: ['notification.container.scss'],
})
export class IonNotificationContainerComponent {
constructor(private renderer: Renderer2, private element: ElementRef) {}

addNotification(notification: ComponentRef<IonNotificationComponent>): void {
notification.instance.ionOnClose.subscribe(() => {
this.removeNotification(notification.location.nativeElement);
});

this.renderer.appendChild(
this.element.nativeElement,
notification.location.nativeElement
);
}

removeNotification(notification: ElementRef): void {
this.renderer.removeChild(this.element.nativeElement, notification);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
:host {
position: absolute;
display: flex;
flex-direction: column;
top: 0;
right: 0;
gap: 1rem;
}
164 changes: 164 additions & 0 deletions projects/ion/src/lib/notification/service/notification.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
import { IonSharedModule } from './../../shared.module';
import { IonNotificationContainerComponent } from './notification.container.component';
import { IonNotificationComponent } from '../component/notification.component';
import { IonNotificationService } from './notification.service';
import { TestBed } from '@angular/core/testing';
import { Component, NgModule } from '@angular/core';
import { fireEvent, screen } from '@testing-library/angular';

const NOTIFICATION_ICONS = {
success: 'success-icon',
info: 'info-icon',
warning: 'warning-icon',
negative: 'negative-icon',
};

const NOTIFICATION_TYPES = Object.keys(NOTIFICATION_ICONS);

const DEFAULT_NOTIFICATION_OPTIONS = {
title: 'Titulo Padrão',
message: 'Mensagem Padrão',
};

@Component({
template: '<div></div>',
})
class ContainerRefTestComponent {}

@NgModule({
declarations: [
ContainerRefTestComponent,
IonNotificationContainerComponent,
IonNotificationComponent,
],
imports: [IonSharedModule],
entryComponents: [
ContainerRefTestComponent,
IonNotificationContainerComponent,
IonNotificationComponent,
],
})
class TestModule {}

jest.setTimeout(1000);

describe('NotificationService', () => {
let notificationService: IonNotificationService;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [TestModule],
}).compileComponents();

notificationService = TestBed.get(IonNotificationService);
});

it('should remove a notification', () => {
notificationService.success(
DEFAULT_NOTIFICATION_OPTIONS.title,
DEFAULT_NOTIFICATION_OPTIONS.message,
{ fixed: true }
);
const removeNotification = screen.getByTestId('btn-remove');
fireEvent.click(removeNotification);
const elements = document.getElementsByTagName('ion-notification');
expect(elements).toHaveLength(0);
});

it('should emit event when a notification is closed', () => {
const closeEvent = jest.fn();
notificationService.success(
DEFAULT_NOTIFICATION_OPTIONS.title,
DEFAULT_NOTIFICATION_OPTIONS.message,
{ fixed: true },
closeEvent
);
const removeNotification = screen.getByTestId('btn-remove');
fireEvent.click(removeNotification);
expect(closeEvent).toHaveBeenCalledTimes(1);
});

it('should create a notification', () => {
notificationService.success(
DEFAULT_NOTIFICATION_OPTIONS.title,
DEFAULT_NOTIFICATION_OPTIONS.message
);
expect(screen.getByTestId('ion-notification')).toBeTruthy();
});

it.each(['title', 'message'])(
'should render a notification with default %s',
(key) => {
expect(
screen.getByText(DEFAULT_NOTIFICATION_OPTIONS[key])
).toBeInTheDocument();
}
);
});

describe('NotificationService -> notification types', () => {
let notificationService: IonNotificationService;

let currentIndex = 1;

let notificationsOnScreen = 5;

const indexToRemove = [1, 2, 0];

const NOTIFICATIONS_CALLS = {
success: (): void => {
notificationService.success('teste', 'teste', {}, () => {
return true;
});
},
info: (): void => {
notificationService.info('teste', 'teste', {}, () => {
return true;
});
},
warning: (): void => {
notificationService.warning('teste', 'teste', {}, () => {
return true;
});
},
negative: (): void => {
notificationService.error('teste', 'teste', {}, () => {
return true;
});
},
};

beforeEach(() => {
TestBed.configureTestingModule({
imports: [TestModule],
}).compileComponents();

notificationService = TestBed.get(IonNotificationService);
});

it.each(NOTIFICATION_TYPES)('should create %s notification', async (type) => {
NOTIFICATIONS_CALLS[type]();
const iconType = screen.getAllByTestId('notification-icon');
expect(iconType[currentIndex]).toHaveClass(NOTIFICATION_ICONS[type]);
currentIndex += 1;
});

it.each(indexToRemove)(
'should remove multiple notifications',
async (index) => {
const elements = document.getElementsByTagName('ion-notification');
const removeNotification = screen.getAllByTestId('btn-remove');
fireEvent.click(removeNotification[index]);
notificationsOnScreen -= 1;
expect(elements).toHaveLength(notificationsOnScreen);
}
);

it.each(NOTIFICATION_TYPES)(
'should add ionOnClose subscription when %s notification is created',
async (type) => {
notificationService.addCloseEventEmitter = jest.fn();
NOTIFICATIONS_CALLS[type]();
expect(notificationService.addCloseEventEmitter).toHaveBeenCalledTimes(1);
}
);
});
Loading

0 comments on commit c8b43d1

Please sign in to comment.