feat(product-list): implement pagination and update items to use paginated response
This commit is contained in:
@@ -3,7 +3,7 @@
|
|||||||
[class.product-list--row]="layout() === 'row'"
|
[class.product-list--row]="layout() === 'row'"
|
||||||
[class.product-list--column]="layout() !== 'row'"
|
[class.product-list--column]="layout() !== 'row'"
|
||||||
>
|
>
|
||||||
@for (item of items(); track item.id; let index = $index) {
|
@for (item of items().data; track item.id; let index = $index) {
|
||||||
<div class="product-list__item">
|
<div class="product-list__item">
|
||||||
@switch (layout()) {
|
@switch (layout()) {
|
||||||
@case ('row') {
|
@case ('row') {
|
||||||
@@ -38,3 +38,14 @@
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@if (items().meta.last_page > 1) {
|
||||||
|
<div class="product-list__paginator d-flex justify-content-center">
|
||||||
|
<app-paginator
|
||||||
|
[page]="items().meta.current_page"
|
||||||
|
[totalPages]="items().meta.last_page"
|
||||||
|
[disabled]="loading()"
|
||||||
|
(pageChange)="pageChange.emit($event)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|||||||
@@ -25,3 +25,7 @@
|
|||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.product-list__paginator {
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { TestBed, getTestBed } from '@angular/core/testing';
|
import { TestBed, getTestBed } from '@angular/core/testing';
|
||||||
import { BrowserTestingModule, platformBrowserTesting } from '@angular/platform-browser/testing';
|
import { BrowserTestingModule, platformBrowserTesting } from '@angular/platform-browser/testing';
|
||||||
import { afterEach, beforeAll, describe, expect, it } from 'vitest';
|
import { afterEach, beforeAll, describe, expect, it, vi } from 'vitest';
|
||||||
|
|
||||||
|
import { ApiPaginatedResponse } from '../../../core/services/api-paginated-response.interface';
|
||||||
import { ProductListComponent, ProductListItem, ProductListLayout } from './product-list.component';
|
import { ProductListComponent, ProductListItem, ProductListLayout } from './product-list.component';
|
||||||
|
|
||||||
describe('ProductListComponent', () => {
|
describe('ProductListComponent', () => {
|
||||||
@@ -38,12 +39,34 @@ describe('ProductListComponent', () => {
|
|||||||
await TestBed.configureTestingModule({ imports: [ProductListComponent] }).compileComponents();
|
await TestBed.configureTestingModule({ imports: [ProductListComponent] }).compileComponents();
|
||||||
const fixture = TestBed.createComponent(ProductListComponent);
|
const fixture = TestBed.createComponent(ProductListComponent);
|
||||||
fixture.componentRef.setInput('layout', layout);
|
fixture.componentRef.setInput('layout', layout);
|
||||||
fixture.componentRef.setInput('items', items);
|
fixture.componentRef.setInput('items', paginatedItems());
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
return fixture;
|
return fixture;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function paginatedItems(currentPage = 1, lastPage = 2): ApiPaginatedResponse<ProductListItem[]> {
|
||||||
|
return {
|
||||||
|
data: items,
|
||||||
|
meta: {
|
||||||
|
current_page: currentPage,
|
||||||
|
from: 1,
|
||||||
|
last_page: lastPage,
|
||||||
|
links: [],
|
||||||
|
path: '/catalog/featured-groups/1/items',
|
||||||
|
per_page: 12,
|
||||||
|
to: 2,
|
||||||
|
total: 13,
|
||||||
|
},
|
||||||
|
links: {
|
||||||
|
first: '/catalog/featured-groups/1/items?page=1',
|
||||||
|
last: '/catalog/featured-groups/1/items?page=2',
|
||||||
|
prev: null,
|
||||||
|
next: '/catalog/featured-groups/1/items?page=2',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
it('renders every row product at full available width', async () => {
|
it('renders every row product at full available width', async () => {
|
||||||
const fixture = await render('row');
|
const fixture = await render('row');
|
||||||
const element = fixture.nativeElement as HTMLElement;
|
const element = fixture.nativeElement as HTMLElement;
|
||||||
@@ -70,4 +93,17 @@ describe('ProductListComponent', () => {
|
|||||||
),
|
),
|
||||||
).toHaveLength(2);
|
).toHaveLength(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('renders pagination and emits the requested page', async () => {
|
||||||
|
const fixture = await render('row');
|
||||||
|
const pageChangeSpy = vi.fn();
|
||||||
|
fixture.componentInstance.pageChange.subscribe(pageChangeSpy);
|
||||||
|
|
||||||
|
const nextButton = (fixture.nativeElement as HTMLElement).querySelector(
|
||||||
|
'[data-testid="paginator-next"]',
|
||||||
|
) as HTMLButtonElement;
|
||||||
|
nextButton.click();
|
||||||
|
|
||||||
|
expect(pageChangeSpy).toHaveBeenCalledWith(2);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { ChangeDetectionStrategy, Component, input, output } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, input, output } from '@angular/core';
|
||||||
|
|
||||||
|
import { ApiPaginatedResponse } from '../../../core/services/api-paginated-response.interface';
|
||||||
|
import { PaginatorComponent } from '../paginator/paginator.component';
|
||||||
import { ProductColumnWithImageComponent } from '../product-column-with-image/product-column-with-image.component';
|
import { ProductColumnWithImageComponent } from '../product-column-with-image/product-column-with-image.component';
|
||||||
import {
|
import {
|
||||||
ProductRowCardComponent,
|
ProductRowCardComponent,
|
||||||
@@ -35,6 +37,7 @@ export interface ProductListCartEvent {
|
|||||||
selector: 'app-product-list',
|
selector: 'app-product-list',
|
||||||
imports: [
|
imports: [
|
||||||
ProductColumnWithImageComponent,
|
ProductColumnWithImageComponent,
|
||||||
|
PaginatorComponent,
|
||||||
ProductRowCardComponent,
|
ProductRowCardComponent,
|
||||||
ProductVerticalWithCartCardComponent,
|
ProductVerticalWithCartCardComponent,
|
||||||
],
|
],
|
||||||
@@ -44,10 +47,12 @@ export interface ProductListCartEvent {
|
|||||||
})
|
})
|
||||||
export class ProductListComponent {
|
export class ProductListComponent {
|
||||||
readonly layout = input.required<ProductListLayout>();
|
readonly layout = input.required<ProductListLayout>();
|
||||||
readonly items = input.required<readonly ProductListItem[]>();
|
readonly items = input.required<ApiPaginatedResponse<ProductListItem[]>>();
|
||||||
|
readonly loading = input(false);
|
||||||
|
|
||||||
readonly buy = output<ProductListItem>();
|
readonly buy = output<ProductListItem>();
|
||||||
readonly addToCart = output<ProductListCartEvent>();
|
readonly addToCart = output<ProductListCartEvent>();
|
||||||
|
readonly pageChange = output<number>();
|
||||||
|
|
||||||
protected price(item: ProductListItem): number {
|
protected price(item: ProductListItem): number {
|
||||||
const price = Number(item.precio);
|
const price = Number(item.precio);
|
||||||
|
|||||||
Reference in New Issue
Block a user