Compare commits
6 Commits
develop
...
feature/re
| Author | SHA1 | Date | |
|---|---|---|---|
| a7e1d8995f | |||
| b5fae81795 | |||
| 8664c86915 | |||
| 9e72ec0c60 | |||
| e9aae7a4c3 | |||
| 6545be498f |
@@ -1,14 +1,12 @@
|
||||
import '@angular/compiler';
|
||||
import { signal } from '@angular/core';
|
||||
import { TestBed, getTestBed } from '@angular/core/testing';
|
||||
import {
|
||||
BrowserTestingModule,
|
||||
platformBrowserTesting
|
||||
} from '@angular/platform-browser/testing';
|
||||
import { BrowserTestingModule, platformBrowserTesting } from '@angular/platform-browser/testing';
|
||||
import { provideRouter } from '@angular/router';
|
||||
import { afterEach, beforeAll, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { App } from './app';
|
||||
import { EventDateNoticeService } from './core/services/event-date-notice.service';
|
||||
import { Tenant } from './core/services/tenant.interface';
|
||||
import { TenantService } from './core/services/tenant.service';
|
||||
|
||||
@@ -27,7 +25,7 @@ const tenant: Tenant = {
|
||||
footer_bg_color: '#313131',
|
||||
header_logo: 'https://example.com/header.png',
|
||||
footer_logo: 'https://example.com/footer.png',
|
||||
categories: []
|
||||
categories: [],
|
||||
};
|
||||
|
||||
function createTenantServiceStub(status: 'ready' | 'not-found', currentTenant: Tenant | null) {
|
||||
@@ -38,17 +36,14 @@ function createTenantServiceStub(status: 'ready' | 'not-found', currentTenant: T
|
||||
tenant: tenantState.asReadonly(),
|
||||
status: statusState.asReadonly(),
|
||||
getTenant: () => tenantState(),
|
||||
bootstrap: vi.fn().mockResolvedValue(undefined)
|
||||
bootstrap: vi.fn().mockResolvedValue(undefined),
|
||||
};
|
||||
}
|
||||
|
||||
describe('App', () => {
|
||||
beforeAll(() => {
|
||||
try {
|
||||
getTestBed().initTestEnvironment(
|
||||
BrowserTestingModule,
|
||||
platformBrowserTesting()
|
||||
);
|
||||
getTestBed().initTestEnvironment(BrowserTestingModule, platformBrowserTesting());
|
||||
} catch {
|
||||
// Test environment may already be initialized by another setup entrypoint.
|
||||
}
|
||||
@@ -65,9 +60,9 @@ describe('App', () => {
|
||||
provideRouter([]),
|
||||
{
|
||||
provide: TenantService,
|
||||
useValue: createTenantServiceStub('ready', tenant)
|
||||
}
|
||||
]
|
||||
useValue: createTenantServiceStub('ready', tenant),
|
||||
},
|
||||
],
|
||||
}).compileComponents();
|
||||
|
||||
const fixture = TestBed.createComponent(App);
|
||||
@@ -75,26 +70,27 @@ describe('App', () => {
|
||||
|
||||
expect(fixture.componentInstance).toBeTruthy();
|
||||
expect(fixture.nativeElement.style.getPropertyValue('--tenant-primary')).toBe(
|
||||
tenant.primary_color
|
||||
tenant.primary_color,
|
||||
);
|
||||
expect(fixture.nativeElement.style.getPropertyValue('--tenant-secondary')).toBe(
|
||||
tenant.secondary_color
|
||||
tenant.secondary_color,
|
||||
);
|
||||
expect(fixture.nativeElement.style.getPropertyValue('--tenant-danger')).toBe(
|
||||
tenant.danger_color
|
||||
tenant.danger_color,
|
||||
);
|
||||
expect(fixture.nativeElement.style.getPropertyValue('--tenant-primary-rgb')).toBe(
|
||||
'99, 118, 243'
|
||||
'99, 118, 243',
|
||||
);
|
||||
expect(fixture.nativeElement.style.getPropertyValue('--tenant-secondary-rgb')).toBe(
|
||||
'160, 160, 160'
|
||||
'160, 160, 160',
|
||||
);
|
||||
expect(fixture.nativeElement.style.getPropertyValue('--tenant-danger-rgb')).toBe(
|
||||
'255, 136, 136'
|
||||
'255, 136, 136',
|
||||
);
|
||||
expect(document.title).toBe(tenant.site_title);
|
||||
expect(document.head.querySelector<HTMLLinkElement>('link[rel~="icon"]')?.getAttribute('href'))
|
||||
.toBe(tenant.favicon);
|
||||
expect(
|
||||
document.head.querySelector<HTMLLinkElement>('link[rel~="icon"]')?.getAttribute('href'),
|
||||
).toBe(tenant.favicon);
|
||||
});
|
||||
|
||||
it('renders the tenant not found screen when the tenant is missing', async () => {
|
||||
@@ -104,17 +100,65 @@ describe('App', () => {
|
||||
provideRouter([]),
|
||||
{
|
||||
provide: TenantService,
|
||||
useValue: createTenantServiceStub('not-found', null)
|
||||
}
|
||||
]
|
||||
useValue: createTenantServiceStub('not-found', null),
|
||||
},
|
||||
],
|
||||
}).compileComponents();
|
||||
|
||||
const fixture = TestBed.createComponent(App);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(fixture.nativeElement.textContent).toContain('No encontramos una tienda para este dominio');
|
||||
expect(fixture.nativeElement.textContent).toContain(
|
||||
'No encontramos una tienda para este dominio',
|
||||
);
|
||||
expect(document.title).toBe('ShopitFront');
|
||||
expect(document.head.querySelector<HTMLLinkElement>('link[rel~="icon"]')?.getAttribute('href'))
|
||||
.toBe("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'/%3E");
|
||||
expect(
|
||||
document.head.querySelector<HTMLLinkElement>('link[rel~="icon"]')?.getAttribute('href'),
|
||||
).toBe("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'/%3E");
|
||||
});
|
||||
|
||||
it('shows the date notices returned by the tenant bootstrap once', async () => {
|
||||
const show = vi.fn();
|
||||
const dateNotices = [
|
||||
{
|
||||
type: 'suspended' as const,
|
||||
title: 'FECHA CANCELADA!',
|
||||
message: [
|
||||
{ text: 'La fecha del ', bold: false },
|
||||
{ text: '09 de Octubre de 2026', bold: true },
|
||||
{ text: ' ha sido cancelada.', bold: false },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [App],
|
||||
providers: [
|
||||
provideRouter([]),
|
||||
{
|
||||
provide: TenantService,
|
||||
useValue: createTenantServiceStub('ready', {
|
||||
...tenant,
|
||||
event: {
|
||||
title: 'Festival',
|
||||
location: 'Predio',
|
||||
dates: [],
|
||||
date_notices: dateNotices,
|
||||
},
|
||||
}),
|
||||
},
|
||||
{
|
||||
provide: EventDateNoticeService,
|
||||
useValue: { show },
|
||||
},
|
||||
],
|
||||
}).compileComponents();
|
||||
|
||||
const fixture = TestBed.createComponent(App);
|
||||
fixture.detectChanges();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(show).toHaveBeenCalledOnce();
|
||||
expect(show).toHaveBeenCalledWith(dateNotices);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { DOCUMENT } from '@angular/common';
|
||||
import { Component, computed, effect, inject } from '@angular/core';
|
||||
import { DOCUMENT, isPlatformBrowser } from '@angular/common';
|
||||
import { Component, PLATFORM_ID, computed, effect, inject } from '@angular/core';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import { RouterOutlet } from '@angular/router';
|
||||
|
||||
import { EventDateNoticeService } from './core/services/event-date-notice.service';
|
||||
import { TenantService } from './core/services/tenant.service';
|
||||
import { DEFAULT_TENANT_BRANDING } from './core/services/tenant-ssr-cache.store';
|
||||
import { GlobalLoadingComponent } from './shared/components/global-loading/global-loading.component';
|
||||
@@ -13,7 +14,9 @@ const EMPTY_FAVICON = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/s
|
||||
|
||||
function hexToRgb(hex: string): string {
|
||||
const cleanHex = hex.replace('#', '').trim();
|
||||
let r = 0, g = 0, b = 0;
|
||||
let r = 0,
|
||||
g = 0,
|
||||
b = 0;
|
||||
if (cleanHex.length === 3) {
|
||||
r = parseInt(cleanHex[0] + cleanHex[0], 16);
|
||||
g = parseInt(cleanHex[1] + cleanHex[1], 16);
|
||||
@@ -28,12 +31,7 @@ function hexToRgb(hex: string): string {
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
imports: [
|
||||
RouterOutlet,
|
||||
ToastContainerComponent,
|
||||
ModalHostComponent,
|
||||
GlobalLoadingComponent
|
||||
],
|
||||
imports: [RouterOutlet, ToastContainerComponent, ModalHostComponent, GlobalLoadingComponent],
|
||||
templateUrl: './app.html',
|
||||
styleUrl: './app.scss',
|
||||
host: {
|
||||
@@ -56,13 +54,16 @@ function hexToRgb(hex: string): string {
|
||||
'[style.--tenant-header-bg]': 'tenantHeaderBgColor()',
|
||||
'[style.--tenant-footer-bg]': 'tenantFooterBgColor()',
|
||||
'[style.--color-header-bg]': 'tenantHeaderBgColor()',
|
||||
'[style.--color-footer-bg]': 'tenantFooterBgColor()'
|
||||
}
|
||||
'[style.--color-footer-bg]': 'tenantFooterBgColor()',
|
||||
},
|
||||
})
|
||||
export class App {
|
||||
private readonly tenantService = inject(TenantService);
|
||||
private readonly eventDateNoticeService = inject(EventDateNoticeService);
|
||||
private readonly document = inject(DOCUMENT);
|
||||
private readonly title = inject(Title);
|
||||
private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
|
||||
private notifiedTenantCode: string | null = null;
|
||||
|
||||
constructor() {
|
||||
effect(() => {
|
||||
@@ -81,43 +82,38 @@ export class App {
|
||||
}
|
||||
|
||||
favicon.setAttribute('href', faviconHref);
|
||||
|
||||
if (this.isBrowser && tenant && this.notifiedTenantCode !== tenant.codigo) {
|
||||
this.notifiedTenantCode = tenant.codigo;
|
||||
this.eventDateNoticeService.show(tenant.event?.date_notices ?? []);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected readonly status = this.tenantService.status;
|
||||
|
||||
protected readonly tenantPrimaryColor = computed(
|
||||
() => this.tenantService.tenant()?.primary_color ?? DEFAULT_TENANT_BRANDING.primaryColor
|
||||
() => this.tenantService.tenant()?.primary_color ?? DEFAULT_TENANT_BRANDING.primaryColor,
|
||||
);
|
||||
protected readonly tenantSecondaryColor = computed(
|
||||
() => this.tenantService.tenant()?.secondary_color ?? DEFAULT_TENANT_BRANDING.secondaryColor
|
||||
() => this.tenantService.tenant()?.secondary_color ?? DEFAULT_TENANT_BRANDING.secondaryColor,
|
||||
);
|
||||
protected readonly tenantDangerColor = computed(
|
||||
() => this.tenantService.tenant()?.danger_color ?? DEFAULT_TENANT_BRANDING.dangerColor
|
||||
() => this.tenantService.tenant()?.danger_color ?? DEFAULT_TENANT_BRANDING.dangerColor,
|
||||
);
|
||||
protected readonly tenantSuccessColor = computed(
|
||||
() => this.tenantService.tenant()?.success_color ?? DEFAULT_TENANT_BRANDING.successColor
|
||||
);
|
||||
protected readonly tenantPrimaryColorRgb = computed(() =>
|
||||
hexToRgb(this.tenantPrimaryColor())
|
||||
() => this.tenantService.tenant()?.success_color ?? DEFAULT_TENANT_BRANDING.successColor,
|
||||
);
|
||||
protected readonly tenantPrimaryColorRgb = computed(() => hexToRgb(this.tenantPrimaryColor()));
|
||||
protected readonly tenantSecondaryColorRgb = computed(() =>
|
||||
hexToRgb(this.tenantSecondaryColor())
|
||||
);
|
||||
protected readonly tenantDangerColorRgb = computed(() =>
|
||||
hexToRgb(this.tenantDangerColor())
|
||||
);
|
||||
protected readonly tenantSuccessColorRgb = computed(() =>
|
||||
hexToRgb(this.tenantSuccessColor())
|
||||
hexToRgb(this.tenantSecondaryColor()),
|
||||
);
|
||||
protected readonly tenantDangerColorRgb = computed(() => hexToRgb(this.tenantDangerColor()));
|
||||
protected readonly tenantSuccessColorRgb = computed(() => hexToRgb(this.tenantSuccessColor()));
|
||||
protected readonly tenantHeaderBgColor = computed(
|
||||
() =>
|
||||
this.tenantService.tenant()?.header_bg_color ??
|
||||
DEFAULT_TENANT_BRANDING.headerBgColor
|
||||
() => this.tenantService.tenant()?.header_bg_color ?? DEFAULT_TENANT_BRANDING.headerBgColor,
|
||||
);
|
||||
protected readonly tenantFooterBgColor = computed(
|
||||
() =>
|
||||
this.tenantService.tenant()?.footer_bg_color ??
|
||||
DEFAULT_TENANT_BRANDING.footerBgColor
|
||||
() => this.tenantService.tenant()?.footer_bg_color ?? DEFAULT_TENANT_BRANDING.footerBgColor,
|
||||
);
|
||||
}
|
||||
|
||||
85
src/app/core/services/event-date-notice.service.spec.ts
Normal file
85
src/app/core/services/event-date-notice.service.spec.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
import '@angular/compiler';
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { getTestBed } from '@angular/core/testing';
|
||||
import { BrowserTestingModule, platformBrowserTesting } from '@angular/platform-browser/testing';
|
||||
import { Subject } from 'rxjs';
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { EventDateNoticeModalComponent } from '../../shared/components/event-date-notice-modal/event-date-notice-modal.component';
|
||||
import { EventDateNoticeService } from './event-date-notice.service';
|
||||
import { ModalService } from './modal.service';
|
||||
import { TenantEventDateNotice } from './tenant.interface';
|
||||
|
||||
describe('EventDateNoticeService', () => {
|
||||
let service: EventDateNoticeService;
|
||||
let open: ReturnType<typeof vi.fn>;
|
||||
|
||||
beforeAll(() => {
|
||||
try {
|
||||
getTestBed().initTestEnvironment(BrowserTestingModule, platformBrowserTesting());
|
||||
} catch {
|
||||
// Test environment may already be initialized by another setup entrypoint.
|
||||
}
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
open = vi.fn();
|
||||
TestBed.configureTestingModule({
|
||||
providers: [
|
||||
EventDateNoticeService,
|
||||
{
|
||||
provide: ModalService,
|
||||
useValue: { open },
|
||||
},
|
||||
],
|
||||
});
|
||||
service = TestBed.inject(EventDateNoticeService);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
TestBed.resetTestingModule();
|
||||
});
|
||||
|
||||
it('shows backend notices sequentially', () => {
|
||||
const firstClosed = new Subject<void | undefined>();
|
||||
const secondClosed = new Subject<void | undefined>();
|
||||
const notices: TenantEventDateNotice[] = [
|
||||
{
|
||||
type: 'suspended',
|
||||
title: 'FECHA CANCELADA!',
|
||||
message: [{ text: 'La fecha fue cancelada.', bold: false }],
|
||||
},
|
||||
{
|
||||
type: 'rescheduled',
|
||||
title: 'FECHA REPROGRAMADA!',
|
||||
message: [{ text: 'La fecha fue reprogramada.', bold: false }],
|
||||
},
|
||||
];
|
||||
|
||||
open
|
||||
.mockReturnValueOnce({ afterClosed$: firstClosed.asObservable() })
|
||||
.mockReturnValueOnce({ afterClosed$: secondClosed.asObservable() });
|
||||
|
||||
service.show(notices);
|
||||
|
||||
expect(open).toHaveBeenCalledTimes(1);
|
||||
expect(open).toHaveBeenNthCalledWith(1, EventDateNoticeModalComponent, {
|
||||
size: 'sm',
|
||||
data: notices[0],
|
||||
});
|
||||
|
||||
firstClosed.next(undefined);
|
||||
|
||||
expect(open).toHaveBeenCalledTimes(2);
|
||||
expect(open).toHaveBeenNthCalledWith(2, EventDateNoticeModalComponent, {
|
||||
size: 'sm',
|
||||
data: notices[1],
|
||||
});
|
||||
});
|
||||
|
||||
it('does not open a modal without notices', () => {
|
||||
service.show([]);
|
||||
|
||||
expect(open).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
32
src/app/core/services/event-date-notice.service.ts
Normal file
32
src/app/core/services/event-date-notice.service.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { Injectable, inject } from '@angular/core';
|
||||
|
||||
import { EventDateNoticeModalComponent } from '../../shared/components/event-date-notice-modal/event-date-notice-modal.component';
|
||||
import { ModalService } from './modal.service';
|
||||
import { TenantEventDateNotice } from './tenant.interface';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class EventDateNoticeService {
|
||||
private readonly modalService = inject(ModalService);
|
||||
|
||||
show(notices: readonly TenantEventDateNotice[]): void {
|
||||
this.showNext(notices, 0);
|
||||
}
|
||||
|
||||
private showNext(notices: readonly TenantEventDateNotice[], index: number): void {
|
||||
const notice = notices[index];
|
||||
|
||||
if (!notice) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.modalService
|
||||
.open<EventDateNoticeModalComponent, void, TenantEventDateNotice>(
|
||||
EventDateNoticeModalComponent,
|
||||
{
|
||||
size: 'sm',
|
||||
data: notice,
|
||||
},
|
||||
)
|
||||
.afterClosed$.subscribe(() => this.showNext(notices, index + 1));
|
||||
}
|
||||
}
|
||||
@@ -80,6 +80,21 @@ describe('ModalService', () => {
|
||||
expect(ref.dismissReason()).toBeNull();
|
||||
});
|
||||
|
||||
it('keeps a modal opened synchronously from an afterClosed subscriber', () => {
|
||||
const firstRef = service.open(FirstTestModalComponent);
|
||||
let secondRef: ModalRef | undefined;
|
||||
|
||||
firstRef.afterClosed$.subscribe(() => {
|
||||
secondRef = service.open(SecondTestModalComponent);
|
||||
});
|
||||
|
||||
firstRef.close();
|
||||
|
||||
expect(secondRef).toBeDefined();
|
||||
expect(service.activeModal()?.ref).toBe(secondRef);
|
||||
expect(service.activeModal()?.component).toBe(SecondTestModalComponent);
|
||||
});
|
||||
|
||||
it('tracks dismiss reasons when closing programmatically', () => {
|
||||
const ref = service.open(FirstTestModalComponent);
|
||||
const closedSpy = vi.fn();
|
||||
|
||||
@@ -253,8 +253,8 @@ export class ModalService {
|
||||
return;
|
||||
}
|
||||
|
||||
ref.finalize(result);
|
||||
this.activeModalState.set(null);
|
||||
ref.finalize(result);
|
||||
}
|
||||
|
||||
private dismiss<TResult>(
|
||||
@@ -265,8 +265,8 @@ export class ModalService {
|
||||
return;
|
||||
}
|
||||
|
||||
ref.finalize(undefined, reason);
|
||||
this.activeModalState.set(null);
|
||||
ref.finalize(undefined, reason);
|
||||
}
|
||||
|
||||
private normalizeConfig<TData>(config: ModalConfig<TData>): NormalizedModalConfig<TData> {
|
||||
|
||||
@@ -69,10 +69,34 @@ export interface ActiveEventDate {
|
||||
time_end: string;
|
||||
}
|
||||
|
||||
export type TenantEventDateChangeType = 'rescheduled' | 'suspended';
|
||||
|
||||
export interface TenantEventDateChange {
|
||||
type: TenantEventDateChangeType;
|
||||
source_event_date_id: number | null;
|
||||
destination_event_date_id: number | null;
|
||||
previous_date: string;
|
||||
new_date: string | null;
|
||||
occurred_at: string;
|
||||
}
|
||||
|
||||
export interface TenantEventDateNoticeMessagePart {
|
||||
text: string;
|
||||
bold: boolean;
|
||||
}
|
||||
|
||||
export interface TenantEventDateNotice {
|
||||
type: TenantEventDateChangeType;
|
||||
title: string;
|
||||
message: TenantEventDateNoticeMessagePart[];
|
||||
}
|
||||
|
||||
export interface TenantEvent {
|
||||
title: string;
|
||||
location: string;
|
||||
dates: ActiveEventDate[];
|
||||
date_changes?: TenantEventDateChange[];
|
||||
date_notices?: TenantEventDateNotice[];
|
||||
}
|
||||
|
||||
export interface WebsiteExtras {
|
||||
@@ -143,6 +167,10 @@ export interface Tenant {
|
||||
cart_editing_policy?: CartEditingPolicy;
|
||||
checkout_editing_policy?: CartEditingPolicy;
|
||||
display_cart_item_images?: boolean;
|
||||
allow_ticket_refund?: boolean;
|
||||
allow_ticket_total_refund?: boolean;
|
||||
allow_ticket_partial_refund?: boolean;
|
||||
ticket_partial_refund_percentage?: string;
|
||||
social_media?: SocialMedia[];
|
||||
menues?: Menu[];
|
||||
categories: Category[];
|
||||
|
||||
@@ -15,6 +15,8 @@ export interface TicketResponse {
|
||||
starts_at: string | null;
|
||||
expires_at: string | null;
|
||||
used_at: string | null;
|
||||
status?: 'active' | 'expired' | 'used' | 'disabled' | 'cancelled' | 'refunded';
|
||||
status_label?: string;
|
||||
is_valid: boolean;
|
||||
is_expired: boolean;
|
||||
is_used: boolean;
|
||||
@@ -26,9 +28,7 @@ export class TicketService extends BaseApiService {
|
||||
|
||||
getTickets(): Promise<TicketResponse[]> {
|
||||
return firstValueFrom(
|
||||
this.http.get<{ data: TicketResponse[] }>(
|
||||
`${this.tenantService.getTenantApiUrl()}/tickets`,
|
||||
),
|
||||
this.http.get<{ data: TicketResponse[] }>(`${this.tenantService.getTenantApiUrl()}/tickets`),
|
||||
).then((response) => response.data ?? []);
|
||||
}
|
||||
|
||||
|
||||
@@ -63,7 +63,11 @@ export class TicketsPage implements OnInit {
|
||||
}
|
||||
|
||||
protected isInactive(ticket: TicketResponse): boolean {
|
||||
return ticket.is_expired || ticket.is_used;
|
||||
return (
|
||||
(ticket.status !== undefined && ticket.status !== 'active') ||
|
||||
ticket.is_expired ||
|
||||
ticket.is_used
|
||||
);
|
||||
}
|
||||
|
||||
protected formatDate(ticket: TicketResponse): string | null {
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
<article class="event-date-notice">
|
||||
<h2 class="event-date-notice__title">{{ data.title }}</h2>
|
||||
|
||||
<p class="event-date-notice__description">
|
||||
@for (part of data.message; track $index) {
|
||||
@if (part.bold) {
|
||||
<strong>{{ part.text }}</strong>
|
||||
} @else {
|
||||
{{ part.text }}
|
||||
}
|
||||
}
|
||||
</p>
|
||||
|
||||
<div class="event-date-notice__actions">
|
||||
<app-button (click)="close()">Cerrar</app-button>
|
||||
</div>
|
||||
</article>
|
||||
@@ -0,0 +1,33 @@
|
||||
.event-date-notice {
|
||||
display: grid;
|
||||
gap: 20px;
|
||||
justify-items: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.event-date-notice__title,
|
||||
.event-date-notice__description {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.event-date-notice__title {
|
||||
color: var(--color-danger, #dc3545);
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
.event-date-notice__description {
|
||||
color: #666666;
|
||||
font-size: 15px;
|
||||
font-weight: 400;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.event-date-notice__description strong {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.event-date-notice__actions {
|
||||
width: 100%;
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
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 { MODAL_DATA, ModalRef } from '../../../core/services/modal.service';
|
||||
import { TenantEventDateNotice } from '../../../core/services/tenant.interface';
|
||||
import { EventDateNoticeModalComponent } from './event-date-notice-modal.component';
|
||||
|
||||
describe('EventDateNoticeModalComponent', () => {
|
||||
beforeAll(() => {
|
||||
try {
|
||||
getTestBed().initTestEnvironment(BrowserTestingModule, platformBrowserTesting());
|
||||
} catch {
|
||||
// Test environment may already be initialized by another setup entrypoint.
|
||||
}
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
TestBed.resetTestingModule();
|
||||
});
|
||||
|
||||
it('renders the precalculated message and emphasizes its marked parts', async () => {
|
||||
const close = vi.fn();
|
||||
const data: TenantEventDateNotice = {
|
||||
type: 'rescheduled',
|
||||
title: 'FECHAS REPROGRAMADAS!',
|
||||
message: [
|
||||
{ text: 'Las fechas del ', bold: false },
|
||||
{ text: '09 y 10 de Octubre de 2026', bold: true },
|
||||
{ text: ' han sido reprogramadas, ', bold: false },
|
||||
{ text: 'respectivamente', bold: true },
|
||||
{ text: '.', bold: false },
|
||||
],
|
||||
};
|
||||
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [EventDateNoticeModalComponent],
|
||||
providers: [
|
||||
{ provide: MODAL_DATA, useValue: data },
|
||||
{ provide: ModalRef, useValue: { close } },
|
||||
],
|
||||
}).compileComponents();
|
||||
|
||||
const fixture = TestBed.createComponent(EventDateNoticeModalComponent);
|
||||
fixture.detectChanges();
|
||||
const element = fixture.nativeElement as HTMLElement;
|
||||
|
||||
expect(element.querySelector('h2')?.textContent).toContain(data.title);
|
||||
expect(
|
||||
[...element.querySelectorAll('strong')].map((strong) => strong.textContent?.trim()),
|
||||
).toEqual(['09 y 10 de Octubre de 2026', 'respectivamente']);
|
||||
|
||||
element.querySelector('button')?.click();
|
||||
expect(close).toHaveBeenCalledOnce();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,21 @@
|
||||
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
|
||||
|
||||
import { MODAL_DATA, ModalRef } from '../../../core/services/modal.service';
|
||||
import { TenantEventDateNotice } from '../../../core/services/tenant.interface';
|
||||
import { ButtonComponent } from '../button/button.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-event-date-notice-modal',
|
||||
imports: [ButtonComponent],
|
||||
templateUrl: './event-date-notice-modal.component.html',
|
||||
styleUrl: './event-date-notice-modal.component.scss',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class EventDateNoticeModalComponent {
|
||||
protected readonly data = inject<TenantEventDateNotice>(MODAL_DATA);
|
||||
private readonly modalRef = inject<ModalRef<void>>(ModalRef);
|
||||
|
||||
protected close(): void {
|
||||
this.modalRef.close();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user