feat(modal): add confirm and confirm delete modals with tests

- Implemented ConfirmModalComponent and ConfirmDeleteModalComponent for confirmation dialogs.
- Added modal service methods to open confirm and confirm delete modals.
- Created corresponding HTML and SCSS files for both modal components.
- Updated ReutilizablesTestPageComponent to utilize new modals.
- Enhanced modal service tests to cover new functionality.
- Refactored existing tests to accommodate changes in modal behavior and structure.
This commit is contained in:
2026-07-02 11:45:47 -03:00
parent b3782a6cc1
commit 446d1b8970
14 changed files with 677 additions and 164 deletions

View File

@@ -0,0 +1,12 @@
<div class="confirm-modal">
<p class="confirm-modal__content">{{ data.content }}</p>
<div class="confirm-modal__actions">
<app-button variant="danger-secondary" (click)="cancel()">
{{ data.cancelLabel }}
</app-button>
<app-button variant="danger" (click)="confirm()">
{{ data.confirmLabel }}
</app-button>
</div>
</div>

View File

@@ -0,0 +1,19 @@
.confirm-modal {
display: grid;
gap: 1.5rem;
justify-items: center;
text-align: center;
}
.confirm-modal__content {
margin: 0;
color: #666666;
line-height: 1.5;
}
.confirm-modal__actions {
display: flex;
justify-content: center;
gap: 0.75rem;
flex-wrap: wrap;
}

View File

@@ -0,0 +1,96 @@
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 {
ConfirmModalData,
MODAL_DATA,
ModalRef
} from '../../../core/services/modal.service';
import { ConfirmDeleteModalComponent } from './confirm-delete-modal.component';
describe('ConfirmDeleteModalComponent', () => {
const data: ConfirmModalData = {
content: 'Se eliminara el elemento seleccionado.',
confirmLabel: 'Eliminar',
cancelLabel: 'Cancelar'
};
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 uses danger for the confirm action', async () => {
await TestBed.configureTestingModule({
imports: [ConfirmDeleteModalComponent],
providers: [
{
provide: MODAL_DATA,
useValue: data
},
{
provide: ModalRef,
useValue: {
close: vi.fn()
}
}
]
}).compileComponents();
const fixture = TestBed.createComponent(ConfirmDeleteModalComponent);
fixture.detectChanges();
const element = fixture.nativeElement as HTMLElement;
const buttons = element.querySelectorAll('button');
expect(element.textContent).toContain(data.content);
expect(element.textContent).toContain(data.confirmLabel);
expect(buttons[1].className).toContain('btn-danger');
});
it('closes with false on cancel and true on confirm', async () => {
const closeSpy = vi.fn();
await TestBed.configureTestingModule({
imports: [ConfirmDeleteModalComponent],
providers: [
{
provide: MODAL_DATA,
useValue: data
},
{
provide: ModalRef,
useValue: {
close: closeSpy
}
}
]
}).compileComponents();
const fixture = TestBed.createComponent(ConfirmDeleteModalComponent);
fixture.detectChanges();
const buttons = fixture.nativeElement.querySelectorAll('button');
buttons[0].click();
buttons[1].click();
expect(closeSpy).toHaveBeenNthCalledWith(1, false);
expect(closeSpy).toHaveBeenNthCalledWith(2, true);
});
});

View File

@@ -0,0 +1,28 @@
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
import {
ConfirmModalData,
MODAL_DATA,
ModalRef
} from '../../../core/services/modal.service';
import { ButtonComponent } from '../button/button.component';
@Component({
selector: 'app-confirm-delete-modal',
imports: [ButtonComponent],
templateUrl: './confirm-delete-modal.component.html',
styleUrl: './confirm-delete-modal.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ConfirmDeleteModalComponent {
protected readonly data = inject<ConfirmModalData>(MODAL_DATA);
private readonly modalRef = inject<ModalRef<boolean>>(ModalRef);
protected cancel(): void {
this.modalRef.close(false);
}
protected confirm(): void {
this.modalRef.close(true);
}
}

View File

@@ -0,0 +1,12 @@
<div class="confirm-modal">
<p class="confirm-modal__content">{{ data.content }}</p>
<div class="confirm-modal__actions">
<app-button variant="secondary" (click)="cancel()">
{{ data.cancelLabel }}
</app-button>
<app-button (click)="confirm()">
{{ data.confirmLabel }}
</app-button>
</div>
</div>

View File

@@ -0,0 +1,19 @@
.confirm-modal {
display: grid;
gap: 1.5rem;
justify-items: center;
text-align: center;
}
.confirm-modal__content {
margin: 0;
color: #666666;
line-height: 1.5;
}
.confirm-modal__actions {
display: flex;
justify-content: center;
gap: 0.75rem;
flex-wrap: wrap;
}

View File

@@ -0,0 +1,95 @@
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 {
ConfirmModalData,
MODAL_DATA,
ModalRef
} from '../../../core/services/modal.service';
import { ConfirmModalComponent } from './confirm-modal.component';
describe('ConfirmModalComponent', () => {
const data: ConfirmModalData = {
content: 'Se confirmara la operacion seleccionada.',
confirmLabel: 'Aceptar',
cancelLabel: 'Volver'
};
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 labels', async () => {
await TestBed.configureTestingModule({
imports: [ConfirmModalComponent],
providers: [
{
provide: MODAL_DATA,
useValue: data
},
{
provide: ModalRef,
useValue: {
close: vi.fn()
}
}
]
}).compileComponents();
const fixture = TestBed.createComponent(ConfirmModalComponent);
fixture.detectChanges();
const element = fixture.nativeElement as HTMLElement;
expect(element.textContent).toContain(data.content);
expect(element.textContent).toContain(data.confirmLabel);
expect(element.textContent).toContain(data.cancelLabel);
});
it('closes with false on cancel and true on confirm', async () => {
const closeSpy = vi.fn();
await TestBed.configureTestingModule({
imports: [ConfirmModalComponent],
providers: [
{
provide: MODAL_DATA,
useValue: data
},
{
provide: ModalRef,
useValue: {
close: closeSpy
}
}
]
}).compileComponents();
const fixture = TestBed.createComponent(ConfirmModalComponent);
fixture.detectChanges();
const buttons = fixture.nativeElement.querySelectorAll('button');
buttons[0].click();
buttons[1].click();
expect(closeSpy).toHaveBeenNthCalledWith(1, false);
expect(closeSpy).toHaveBeenNthCalledWith(2, true);
});
});

View File

@@ -0,0 +1,28 @@
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
import {
ConfirmModalData,
MODAL_DATA,
ModalRef
} from '../../../core/services/modal.service';
import { ButtonComponent } from '../button/button.component';
@Component({
selector: 'app-confirm-modal',
imports: [ButtonComponent],
templateUrl: './confirm-modal.component.html',
styleUrl: './confirm-modal.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ConfirmModalComponent {
protected readonly data = inject<ConfirmModalData>(MODAL_DATA);
private readonly modalRef = inject<ModalRef<boolean>>(ModalRef);
protected cancel(): void {
this.modalRef.close(false);
}
protected confirm(): void {
this.modalRef.close(true);
}
}

View File

@@ -49,16 +49,26 @@
}
.modal-shell__header {
position: relative;
justify-content: center;
align-items: flex-start;
padding: 1.25rem 1.25rem 0.75rem;
}
.modal-shell__title {
margin: 0;
width: 100%;
font-size: clamp(1.15rem, 1rem + 0.4vw, 1.5rem);
font-weight: 700;
line-height: 1.15;
color: #202020;
text-align: center;
}
.modal-shell__header .btn-close {
position: absolute;
top: 1.25rem;
right: 1.25rem;
}
.modal-shell__body {