feat(carousel): add main carousel component with image support and integrate into store home page

This commit is contained in:
2026-07-23 14:27:03 -03:00
parent 5d1ed7efa2
commit b04c33bbff
12 changed files with 476 additions and 1 deletions

View File

@@ -1,3 +1,13 @@
@if (tenant()?.main_carousel_images; as mainCarouselImages) {
@if (mainCarouselImages.length) {
<app-main-carousel
class="store-home__main-carousel"
[images]="mainCarouselImages"
ariaLabel="Imágenes destacadas de la tienda"
/>
}
}
@if (tenant()?.hero_config || tenant()?.event_config) {
<app-hero-banner
[heroConfig]="tenant()?.hero_config"

View File

@@ -2,6 +2,10 @@
display: block;
}
.store-home__main-carousel {
margin-bottom: clamp(2rem, 4vw, 3rem);
}
app-hero-banner + app-store-section,
app-store-section + app-store-section {
margin-top: clamp(3rem, 6vw, 5rem);

View File

@@ -1,3 +1,4 @@
import { signal } from '@angular/core';
import { TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { ActivatedRoute } from '@angular/router';
@@ -11,6 +12,8 @@ import {
CatalogFeaturedItem,
} from '../../../../core/services/catalog/catalog.interface';
import { CatalogService } from '../../../../core/services/catalog/catalog.service';
import { Tenant } from '../../../../core/services/tenant.interface';
import { TenantService } from '../../../../core/services/tenant.service';
import { ToastService } from '../../../../core/services/toast.service';
import { ProductListComponent } from '../../../../shared/components/product-list/product-list.component';
import { StoreHomePageComponent } from './store-home-page.component';
@@ -85,6 +88,33 @@ const pageOneItems: CatalogFeaturedItem[] = [
},
];
function createTenant(mainCarouselImages?: string[]): Tenant {
return {
id: 1,
codigo: 'acme',
nombre: 'Acme',
dominio: 'acme.com',
primary_color: '#6376f3',
secondary_color: '#a0a0a0',
danger_color: '#dc3545',
success_color: '#198754',
header_bg_color: '#ffffff',
footer_bg_color: '#ffffff',
header_logo: '/header.png',
footer_logo: '/footer.png',
main_carousel_images: mainCarouselImages,
};
}
function createTenantServiceStub(tenant: Tenant | null) {
const tenantState = signal(tenant);
return {
tenant: tenantState.asReadonly(),
getTenant: () => tenantState(),
};
}
describe('StoreHomePageComponent', () => {
beforeEach(() => vi.restoreAllMocks());
@@ -115,6 +145,64 @@ describe('StoreHomePageComponent', () => {
);
});
it('renders the tenant main carousel images at the beginning of the home page', async () => {
const mainCarouselImages = [
'https://example.com/carousel-1.webp',
'https://example.com/carousel-2.webp',
];
await TestBed.configureTestingModule({
imports: [StoreHomePageComponent],
providers: [
provideActivatedRoute({ response: createCatalog(), error: null }),
{
provide: CatalogService,
useValue: { getCatalog: vi.fn(), getFeaturedGroupItems: vi.fn() },
},
{
provide: TenantService,
useValue: createTenantServiceStub(createTenant(mainCarouselImages)),
},
],
}).compileComponents();
const fixture = TestBed.createComponent(StoreHomePageComponent);
fixture.detectChanges();
const element = fixture.nativeElement as HTMLElement;
const carousel = element.querySelector('app-main-carousel');
const images = carousel?.querySelectorAll('.main-carousel__image');
expect(element.firstElementChild).toBe(carousel);
expect(images).toHaveLength(2);
expect(images?.[0].getAttribute('src')).toBe(mainCarouselImages[0]);
expect(images?.[1].getAttribute('src')).toBe(mainCarouselImages[1]);
fixture.destroy();
});
it('does not render the main carousel when the tenant has no images', async () => {
await TestBed.configureTestingModule({
imports: [StoreHomePageComponent],
providers: [
provideActivatedRoute({ response: createCatalog(), error: null }),
{
provide: CatalogService,
useValue: { getCatalog: vi.fn(), getFeaturedGroupItems: vi.fn() },
},
{
provide: TenantService,
useValue: createTenantServiceStub(createTenant([])),
},
],
}).compileComponents();
const fixture = TestBed.createComponent(StoreHomePageComponent);
fixture.detectChanges();
expect((fixture.nativeElement as HTMLElement).querySelector('app-main-carousel')).toBeNull();
});
it('requests another page for the selected featured group', async () => {
const pageTwoItems = [{ id: 3, nombre: 'Mouse Gamer', precio: '15999.00', image: null }];
const catalogServiceStub = {

View File

@@ -21,6 +21,7 @@ import {
ProductListItem,
} from '../../../../shared/components/product-list/product-list.component';
import { HeroBannerComponent } from '../../../../shared/components/hero-banner/hero-banner.component';
import { MainCarouselComponent } from '../../../../shared/components/main-carousel/main-carousel.component';
import { StoreSectionComponent } from '../../../../shared/components/store-section/store-section.component';
import {
STORE_HOME_PRODUCTS_ERROR_MESSAGE,
@@ -29,7 +30,12 @@ import {
@Component({
selector: 'app-store-home-page',
imports: [StoreSectionComponent, ProductListComponent, HeroBannerComponent],
imports: [
StoreSectionComponent,
ProductListComponent,
HeroBannerComponent,
MainCarouselComponent,
],
templateUrl: './store-home-page.component.html',
styleUrl: './store-home-page.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,