feat(tickets): show event context in account sidebar
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
<section class="ticket-event-context" aria-labelledby="ticket-event-context-title">
|
||||
@if (event().image; as image) {
|
||||
<img class="ticket-event-context__image" [src]="image" alt="" />
|
||||
}
|
||||
|
||||
<h2 id="ticket-event-context-title" class="ticket-event-context__title">
|
||||
{{ event().title }}
|
||||
</h2>
|
||||
</section>
|
||||
@@ -0,0 +1,24 @@
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.ticket-event-context {
|
||||
padding-top: 1.5rem;
|
||||
border-top: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.ticket-event-context__image {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.ticket-event-context__title {
|
||||
margin: 1rem 0 0;
|
||||
color: #666666;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
line-height: 1.4;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { Component, input } from '@angular/core';
|
||||
|
||||
export interface TicketEventContext {
|
||||
title: string;
|
||||
image: string | null;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-ticket-event-context',
|
||||
templateUrl: './ticket-event-context.component.html',
|
||||
styleUrl: './ticket-event-context.component.scss',
|
||||
})
|
||||
export class TicketEventContextComponent {
|
||||
readonly event = input.required<TicketEventContext>();
|
||||
}
|
||||
@@ -109,3 +109,9 @@
|
||||
}
|
||||
</div>
|
||||
</app-menu-content-section>
|
||||
|
||||
@if (eventId !== undefined) {
|
||||
<ng-template menuPageSidebarContent>
|
||||
<app-ticket-event-context [event]="eventContext()" />
|
||||
</ng-template>
|
||||
}
|
||||
|
||||
@@ -1,18 +1,22 @@
|
||||
import { signal } from '@angular/core';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { TenantService } from '../../../../../../core/services/tenant.service';
|
||||
import { ToastService } from '../../../../../../core/services/toast.service';
|
||||
import { ModalService } from '../../../../../../core/services/modal.service';
|
||||
import { MenuPageSidebarContentService } from '../../../../components/menu-page-layout/menu-page-sidebar-content.service';
|
||||
import { TicketComponent } from './components/ticket/ticket.component';
|
||||
import { TicketResponse, TicketService } from './ticket.service';
|
||||
import { TicketsPage } from './tickets-page';
|
||||
|
||||
function withCustomLoading<T extends object>(service: T): T & { withCustomLoading: () => T } {
|
||||
return Object.assign(service, {
|
||||
withCustomLoading: () => service,
|
||||
const enhancedService = Object.assign({ getTicketEvents: () => Promise.resolve([]) }, service);
|
||||
|
||||
return Object.assign(enhancedService, {
|
||||
withCustomLoading: () => enhancedService,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -300,4 +304,69 @@ describe('TicketsPage', () => {
|
||||
ticket: 'ticket-1',
|
||||
});
|
||||
});
|
||||
|
||||
it('declares contextual test data when tickets belong to an event', async () => {
|
||||
const getTickets = vi.fn().mockResolvedValue([]);
|
||||
const getTicketEvents = vi.fn().mockResolvedValue([
|
||||
{
|
||||
id: 42,
|
||||
title: 'XX Campeonato Mundial de Pelota Vasca',
|
||||
image: 'https://example.com/pelota-vasca.jpg',
|
||||
},
|
||||
]);
|
||||
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [TicketsPage],
|
||||
providers: [
|
||||
MenuPageSidebarContentService,
|
||||
{ provide: ToastService, useValue: { danger: vi.fn() } },
|
||||
{
|
||||
provide: ActivatedRoute,
|
||||
useValue: { snapshot: { paramMap: { get: () => '42' } } },
|
||||
},
|
||||
],
|
||||
})
|
||||
.overrideComponent(TicketsPage, {
|
||||
set: {
|
||||
providers: [
|
||||
{
|
||||
provide: TicketService,
|
||||
useValue: withCustomLoading({ getTickets, getTicketEvents }),
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
const fixture = TestBed.createComponent(TicketsPage);
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
await vi.waitFor(() => expect(getTicketEvents).toHaveBeenCalledOnce());
|
||||
await vi.waitFor(() =>
|
||||
expect(
|
||||
(
|
||||
fixture.componentInstance as unknown as { eventContext: () => { image: string | null } }
|
||||
).eventContext().image,
|
||||
).toBe('https://example.com/pelota-vasca.jpg'),
|
||||
);
|
||||
|
||||
const sidebarContent = TestBed.inject(MenuPageSidebarContentService);
|
||||
const sidebarView = sidebarContent.template()?.createEmbeddedView({});
|
||||
sidebarView?.detectChanges();
|
||||
|
||||
const container = document.createElement('div');
|
||||
sidebarView?.rootNodes.forEach((node) => container.appendChild(node));
|
||||
|
||||
expect(getTickets).toHaveBeenCalledWith(42);
|
||||
expect(container.querySelector('.ticket-event-context')?.textContent).toContain(
|
||||
'XX Campeonato Mundial de Pelota Vasca',
|
||||
);
|
||||
expect(container.querySelector<HTMLImageElement>('.ticket-event-context__image')?.src).toBe(
|
||||
'https://example.com/pelota-vasca.jpg',
|
||||
);
|
||||
expect(container.querySelector('.ticket-event-context__details')).toBeNull();
|
||||
expect(container.querySelector('.ticket-event-context__recommendation')).toBeNull();
|
||||
|
||||
sidebarView?.destroy();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,12 +5,23 @@ import { ModalService } from '../../../../../../core/services/modal.service';
|
||||
import { ToastService } from '../../../../../../core/services/toast.service';
|
||||
import { IconButtonComponent } from '../../../../../../shared/components/icon-button/icon-button.component';
|
||||
import { MenuContentSectionComponent } from '../../../../components/menu-content-section/menu-content-section.component';
|
||||
import { MenuPageSidebarContentDirective } from '../../../../components/menu-page-layout/menu-page-sidebar-content.directive';
|
||||
import {
|
||||
TicketEventContext,
|
||||
TicketEventContextComponent,
|
||||
} from './components/ticket-event-context/ticket-event-context.component';
|
||||
import { TicketComponent } from './components/ticket/ticket.component';
|
||||
import { TicketResponse, TicketService } from './ticket.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-tickets-page',
|
||||
imports: [IconButtonComponent, TicketComponent, MenuContentSectionComponent],
|
||||
imports: [
|
||||
IconButtonComponent,
|
||||
TicketComponent,
|
||||
TicketEventContextComponent,
|
||||
MenuContentSectionComponent,
|
||||
MenuPageSidebarContentDirective,
|
||||
],
|
||||
providers: [TicketService],
|
||||
templateUrl: './tickets-page.html',
|
||||
styleUrl: './tickets-page.scss',
|
||||
@@ -21,6 +32,11 @@ export class TicketsPage implements OnInit {
|
||||
private readonly modalService = inject(ModalService);
|
||||
private readonly route = inject(ActivatedRoute, { optional: true });
|
||||
|
||||
protected readonly eventId = this.eventIdFromRoute();
|
||||
protected readonly eventContext = signal<TicketEventContext>({
|
||||
title: 'XX Campeonato Mundial de Pelota Vasca',
|
||||
image: null,
|
||||
});
|
||||
protected readonly tickets = signal<TicketResponse[]>([]);
|
||||
protected readonly activeTickets = computed(() =>
|
||||
this.tickets().filter((ticket) => !this.isInactive(ticket)),
|
||||
@@ -38,9 +54,25 @@ export class TicketsPage implements OnInit {
|
||||
|
||||
async ngOnInit(): Promise<void> {
|
||||
try {
|
||||
this.tickets.set(
|
||||
await this.ticketService.withCustomLoading().getTickets(this.eventIdFromRoute()),
|
||||
);
|
||||
const service = this.ticketService.withCustomLoading();
|
||||
this.tickets.set(await service.getTickets(this.eventId));
|
||||
|
||||
if (this.eventId !== undefined) {
|
||||
void service
|
||||
.getTicketEvents()
|
||||
.then((events) => {
|
||||
const event = events.find((candidate) => candidate.id === this.eventId);
|
||||
|
||||
if (event) {
|
||||
this.eventContext.update((context) => ({
|
||||
...context,
|
||||
title: event.title,
|
||||
image: event.image,
|
||||
}));
|
||||
}
|
||||
})
|
||||
.catch(() => undefined);
|
||||
}
|
||||
} catch {
|
||||
this.toastService.danger('Hubo un error al cargar los tickets');
|
||||
} finally {
|
||||
|
||||
Reference in New Issue
Block a user