diff --git a/src/app/shared/components/product-list/product-list.component.html b/src/app/shared/components/product-list/product-list.component.html
index c3e9ff2..86568b7 100644
--- a/src/app/shared/components/product-list/product-list.component.html
+++ b/src/app/shared/components/product-list/product-list.component.html
@@ -1,11 +1,11 @@
@for (item of items().data; track item.id; let index = $index) {
- @switch (layout()) {
+ @switch (effectiveLayout()) {
@case ('row') {
{
}
});
- afterEach(() => TestBed.resetTestingModule());
+ afterEach(() => {
+ vi.unstubAllGlobals();
+ TestBed.resetTestingModule();
+ });
async function render(layout: ProductListLayout) {
await TestBed.configureTestingModule({ imports: [ProductListComponent] }).compileComponents();
@@ -76,6 +79,25 @@ describe('ProductListComponent', () => {
expect(element.querySelector('app-product-column-with-image')).toBeNull();
});
+ it('replaces the row layout with column with cart on mobile', async () => {
+ const matchMedia = vi.fn().mockReturnValue({
+ matches: true,
+ addEventListener: vi.fn(),
+ removeEventListener: vi.fn(),
+ } as unknown as MediaQueryList);
+ vi.stubGlobal('matchMedia', matchMedia);
+
+ const fixture = await render('row');
+ await fixture.whenStable();
+ fixture.detectChanges();
+ const element = fixture.nativeElement as HTMLElement;
+
+ expect(matchMedia).toHaveBeenCalledWith('(max-width: 767.98px)');
+ expect(element.querySelector('.product-list--column')).not.toBeNull();
+ expect(element.querySelectorAll('app-product-vertical-with-cart-card')).toHaveLength(2);
+ expect(element.querySelector('app-product-row-card')).toBeNull();
+ });
+
it('renders image products next to each other in the column grid', async () => {
const fixture = await render('column_with_image');
const element = fixture.nativeElement as HTMLElement;
diff --git a/src/app/shared/components/product-list/product-list.component.ts b/src/app/shared/components/product-list/product-list.component.ts
index 4d10101..f231200 100644
--- a/src/app/shared/components/product-list/product-list.component.ts
+++ b/src/app/shared/components/product-list/product-list.component.ts
@@ -1,4 +1,14 @@
-import { ChangeDetectionStrategy, Component, input, output } from '@angular/core';
+import {
+ afterNextRender,
+ ChangeDetectionStrategy,
+ Component,
+ computed,
+ DestroyRef,
+ inject,
+ input,
+ output,
+ signal,
+} from '@angular/core';
import { ApiPaginatedResponse } from '../../../core/services/api-paginated-response.interface';
import {
@@ -37,6 +47,9 @@ export interface ProductListCartEvent {
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ProductListComponent {
+ private readonly destroyRef = inject(DestroyRef);
+ private readonly mobile = signal(false);
+
readonly layout = input.required();
readonly items = input.required>();
readonly loading = input(false);
@@ -45,6 +58,22 @@ export class ProductListComponent {
readonly addToCart = output();
readonly pageChange = output();
+ protected readonly effectiveLayout = computed(() =>
+ this.mobile() && this.layout() === 'row' ? 'column_with_cart' : this.layout(),
+ );
+
+ constructor() {
+ afterNextRender(() => {
+ const mobileQuery = window.matchMedia('(max-width: 767.98px)');
+ const updateMobile = (event: MediaQueryList | MediaQueryListEvent): void =>
+ this.mobile.set(event.matches);
+
+ updateMobile(mobileQuery);
+ mobileQuery.addEventListener('change', updateMobile);
+ this.destroyRef.onDestroy(() => mobileQuery.removeEventListener('change', updateMobile));
+ });
+ }
+
protected price(item: ProductListItem): number {
const price = Number(item.precio);