feat(modal): add simple modal component with open and close functionality
This commit is contained in:
@@ -18,6 +18,7 @@ import { firstValueFrom } from 'rxjs';
|
||||
|
||||
import { ConfirmDeleteModalComponent } from '../../shared/components/confirm-delete-modal/confirm-delete-modal.component';
|
||||
import { ConfirmModalComponent } from '../../shared/components/confirm-modal/confirm-modal.component';
|
||||
import { SimpleModalComponent } from '../../shared/components/simple-modal/simple-modal.component';
|
||||
import {
|
||||
MODAL_DATA,
|
||||
ModalRef,
|
||||
@@ -212,6 +213,41 @@ describe('ModalService', () => {
|
||||
showCloseButton: false
|
||||
});
|
||||
});
|
||||
|
||||
it('opens the simple modal with default button label', () => {
|
||||
service.openSimple({
|
||||
title: 'Aviso',
|
||||
content: 'Este es un aviso simple.'
|
||||
});
|
||||
|
||||
const activeModal = service.activeModal();
|
||||
|
||||
expect(activeModal?.component).toBe(SimpleModalComponent);
|
||||
expect(activeModal?.config).toEqual({
|
||||
title: 'Aviso',
|
||||
data: {
|
||||
content: 'Este es un aviso simple.',
|
||||
buttonLabel: 'Entendido'
|
||||
},
|
||||
size: 'md',
|
||||
closeOnBackdrop: true,
|
||||
closeOnEscape: true,
|
||||
showCloseButton: true
|
||||
});
|
||||
});
|
||||
|
||||
it('maps the simple modal close result to undefined', async () => {
|
||||
const result$ = service.openSimple({
|
||||
title: 'Aviso',
|
||||
content: 'Este es un aviso simple.'
|
||||
});
|
||||
const activeModal = service.activeModal();
|
||||
const resultPromise = firstValueFrom(result$);
|
||||
|
||||
activeModal?.ref.close();
|
||||
|
||||
await expect(resultPromise).resolves.toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
@Component({
|
||||
|
||||
@@ -2,6 +2,7 @@ import { InjectionToken, Injectable, Type, signal } from '@angular/core';
|
||||
import { Observable, Subject, map } from 'rxjs';
|
||||
import { ConfirmDeleteModalComponent } from '../../shared/components/confirm-delete-modal/confirm-delete-modal.component';
|
||||
import { ConfirmModalComponent } from '../../shared/components/confirm-modal/confirm-modal.component';
|
||||
import { SimpleModalComponent } from '../../shared/components/simple-modal/simple-modal.component';
|
||||
|
||||
export type ModalSize = 'sm' | 'md' | 'lg' | 'xl' | 'full';
|
||||
export type ModalDismissReason =
|
||||
@@ -43,6 +44,17 @@ export interface ConfirmModalConfig
|
||||
cancelLabel?: string;
|
||||
}
|
||||
|
||||
export interface SimpleModalData {
|
||||
content: string;
|
||||
buttonLabel: string;
|
||||
}
|
||||
|
||||
export interface SimpleModalConfig
|
||||
extends Omit<ModalConfig<SimpleModalData>, 'data'> {
|
||||
content: string;
|
||||
buttonLabel?: string;
|
||||
}
|
||||
|
||||
export interface ActiveModalState<TResult = unknown, TData = unknown> {
|
||||
component: Type<unknown>;
|
||||
config: NormalizedModalConfig<TData>;
|
||||
@@ -158,6 +170,19 @@ export class ModalService {
|
||||
);
|
||||
}
|
||||
|
||||
openSimple(config: SimpleModalConfig): Observable<void> {
|
||||
return this.openSimpleRef(config).afterClosed$.pipe(
|
||||
map(() => undefined)
|
||||
);
|
||||
}
|
||||
|
||||
openSimpleRef(config: SimpleModalConfig): ModalRef<void> {
|
||||
return this.open(
|
||||
SimpleModalComponent,
|
||||
this.buildSimpleModalConfig(config)
|
||||
);
|
||||
}
|
||||
|
||||
private close<TResult>(ref: ModalRef<TResult>, result?: TResult): void {
|
||||
if (this.activeModalState()?.ref !== ref) {
|
||||
return;
|
||||
@@ -207,4 +232,22 @@ export class ModalService {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private buildSimpleModalConfig(
|
||||
config: SimpleModalConfig
|
||||
): ModalConfig<SimpleModalData> {
|
||||
const {
|
||||
content,
|
||||
buttonLabel = 'Entendido',
|
||||
...modalConfig
|
||||
} = config;
|
||||
|
||||
return {
|
||||
...modalConfig,
|
||||
data: {
|
||||
content,
|
||||
buttonLabel
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,6 +91,9 @@
|
||||
<app-button variant="danger-secondary" (click)="openWideModal()">
|
||||
Abrir modal ancho
|
||||
</app-button>
|
||||
<app-button (click)="openSimpleModal()">
|
||||
Abrir simple modal
|
||||
</app-button>
|
||||
</div>
|
||||
<div class="modal-showcase__result" data-testid="modal-last-result">
|
||||
{{ lastModalResult }}
|
||||
|
||||
@@ -226,6 +226,16 @@ export class ReutilizablesTestPageComponent {
|
||||
});
|
||||
}
|
||||
|
||||
protected openSimpleModal(): void {
|
||||
this.modalService.openSimple({
|
||||
title: 'Mensaje del sistema',
|
||||
content: 'Este es un mensaje simple del sistema que no requiere confirmación.',
|
||||
buttonLabel: 'Entendido'
|
||||
}).subscribe(() => {
|
||||
this.lastModalResult = 'Simple modal cerrado';
|
||||
});
|
||||
}
|
||||
|
||||
private openConfirmModal(config: Parameters<ModalService['openConfirm']>[0]): void {
|
||||
this.modalService.openConfirm(config).subscribe((confirmed) => {
|
||||
this.lastModalResult = `Resultado: ${confirmed}`;
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
<div class="simple-modal">
|
||||
<p class="simple-modal__content">{{ data.content }}</p>
|
||||
|
||||
<div class="simple-modal__actions">
|
||||
<app-button (click)="close()">
|
||||
{{ data.buttonLabel }}
|
||||
</app-button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,19 @@
|
||||
.simple-modal {
|
||||
display: grid;
|
||||
gap: 1.5rem;
|
||||
justify-items: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.simple-modal__content {
|
||||
margin: 0;
|
||||
color: #666666;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.simple-modal__actions {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 0.75rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
import '@angular/compiler';
|
||||
import { TestBed, getTestBed } from '@angular/core/testing';
|
||||
import {
|
||||
BrowserTestingModule,
|
||||
platformBrowserTesting
|
||||
} from '@angular/platform-browser/testing';
|
||||
import { afterEach, beforeAll, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import {
|
||||
SimpleModalData,
|
||||
MODAL_DATA,
|
||||
ModalRef
|
||||
} from '../../../core/services/modal.service';
|
||||
import { SimpleModalComponent } from './simple-modal.component';
|
||||
|
||||
describe('SimpleModalComponent', () => {
|
||||
const data: SimpleModalData = {
|
||||
content: 'Este es un mensaje simple.',
|
||||
buttonLabel: 'Entendido'
|
||||
};
|
||||
|
||||
beforeAll(() => {
|
||||
try {
|
||||
getTestBed().initTestEnvironment(
|
||||
BrowserTestingModule,
|
||||
platformBrowserTesting()
|
||||
);
|
||||
} catch {
|
||||
// Test environment may already be initialized by another setup entrypoint.
|
||||
}
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
TestBed.resetTestingModule();
|
||||
});
|
||||
|
||||
it('renders the configured content and button label', async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [SimpleModalComponent],
|
||||
providers: [
|
||||
{
|
||||
provide: MODAL_DATA,
|
||||
useValue: data
|
||||
},
|
||||
{
|
||||
provide: ModalRef,
|
||||
useValue: {
|
||||
close: vi.fn()
|
||||
}
|
||||
}
|
||||
]
|
||||
}).compileComponents();
|
||||
|
||||
const fixture = TestBed.createComponent(SimpleModalComponent);
|
||||
fixture.detectChanges();
|
||||
|
||||
const element = fixture.nativeElement as HTMLElement;
|
||||
|
||||
expect(element.textContent).toContain(data.content);
|
||||
expect(element.textContent).toContain(data.buttonLabel);
|
||||
});
|
||||
|
||||
it('closes when clicking the primary button', async () => {
|
||||
const closeSpy = vi.fn();
|
||||
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [SimpleModalComponent],
|
||||
providers: [
|
||||
{
|
||||
provide: MODAL_DATA,
|
||||
useValue: data
|
||||
},
|
||||
{
|
||||
provide: ModalRef,
|
||||
useValue: {
|
||||
close: closeSpy
|
||||
}
|
||||
}
|
||||
]
|
||||
}).compileComponents();
|
||||
|
||||
const fixture = TestBed.createComponent(SimpleModalComponent);
|
||||
fixture.detectChanges();
|
||||
|
||||
const button = fixture.nativeElement.querySelector('button');
|
||||
button.click();
|
||||
|
||||
expect(closeSpy).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,24 @@
|
||||
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
|
||||
|
||||
import {
|
||||
SimpleModalData,
|
||||
MODAL_DATA,
|
||||
ModalRef
|
||||
} from '../../../core/services/modal.service';
|
||||
import { ButtonComponent } from '../button/button.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-simple-modal',
|
||||
imports: [ButtonComponent],
|
||||
templateUrl: './simple-modal.component.html',
|
||||
styleUrl: './simple-modal.component.scss',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class SimpleModalComponent {
|
||||
protected readonly data = inject<SimpleModalData>(MODAL_DATA);
|
||||
private readonly modalRef = inject<ModalRef<void>>(ModalRef);
|
||||
|
||||
protected close(): void {
|
||||
this.modalRef.close();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user