feat(product-detail): enhance product variant handling with inventory policies and update UI components

This commit is contained in:
2026-07-15 14:33:39 -03:00
parent 25085f6f85
commit e4195a8567
9 changed files with 419 additions and 166 deletions

View File

@@ -1,4 +1,8 @@
<div class="quantity-selector d-inline-flex align-items-center overflow-hidden rounded" [class.quantity-selector--small]="size() === 'small'" aria-label="Selector de cantidad">
<div
class="quantity-selector d-inline-flex align-items-center overflow-hidden rounded"
[class.quantity-selector--small]="size() === 'small'"
aria-label="Selector de cantidad"
>
<button
type="button"
class="quantity-selector__button border-0 p-0 fw-bold lh-1"
@@ -9,13 +13,16 @@
-
</button>
<span class="quantity-selector__value flex-grow-1 min-w-0 text-center fw-bold lh-1 user-select-none">{{ quantity() }}</span>
<span
class="quantity-selector__value flex-grow-1 min-w-0 text-center fw-bold lh-1 user-select-none"
>{{ quantity() }}</span
>
<button
type="button"
class="quantity-selector__button border-0 p-0 fw-bold lh-1"
aria-label="Aumentar cantidad"
[disabled]="quantity() >= max()"
[disabled]="atMaximum()"
(click)="onIncrease()"
>
+

View File

@@ -0,0 +1,55 @@
import { TestBed } from '@angular/core/testing';
import { beforeEach, describe, expect, it } from 'vitest';
import { QuantitySelectorComponent } from './quantity-selector.component';
describe('QuantitySelectorComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [QuantitySelectorComponent],
}).compileComponents();
});
it('stops increasing when it reaches a numeric maximum', () => {
const fixture = TestBed.createComponent(QuantitySelectorComponent);
fixture.componentRef.setInput('max', 2);
fixture.detectChanges();
const increaseButton = fixture.nativeElement.querySelector(
'button:last-child',
) as HTMLButtonElement;
increaseButton.click();
fixture.detectChanges();
expect(fixture.componentInstance.quantity()).toBe(2);
expect(increaseButton.disabled).toBe(true);
});
it('keeps increasing when maximum is null', () => {
const fixture = TestBed.createComponent(QuantitySelectorComponent);
fixture.componentRef.setInput('max', null);
fixture.detectChanges();
const increaseButton = fixture.nativeElement.querySelector(
'button:last-child',
) as HTMLButtonElement;
increaseButton.click();
increaseButton.click();
fixture.detectChanges();
expect(fixture.componentInstance.quantity()).toBe(3);
expect(increaseButton.disabled).toBe(false);
});
it('does not decrease below the configured minimum', () => {
const fixture = TestBed.createComponent(QuantitySelectorComponent);
fixture.detectChanges();
const decreaseButton = fixture.nativeElement.querySelector(
'button:first-child',
) as HTMLButtonElement;
expect(decreaseButton.disabled).toBe(true);
expect(fixture.componentInstance.quantity()).toBe(1);
});
});

View File

@@ -1,4 +1,4 @@
import { ChangeDetectionStrategy, Component, input, model, output } from '@angular/core';
import { ChangeDetectionStrategy, Component, computed, input, model, output } from '@angular/core';
@Component({
selector: 'app-quantity-selector',
@@ -6,14 +6,19 @@ import { ChangeDetectionStrategy, Component, input, model, output } from '@angul
imports: [],
templateUrl: './quantity-selector.component.html',
styleUrl: './quantity-selector.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class QuantitySelectorComponent {
readonly quantity = model<number>(1);
readonly min = input<number>(1);
readonly max = input<number>(100);
readonly max = input<number | null>(100);
readonly size = input<'small' | 'medium'>('medium');
protected readonly atMaximum = computed(() => {
const max = this.max();
return max !== null && this.quantity() >= max;
});
readonly increase = output<void>();
readonly decrease = output<void>();
@@ -25,7 +30,7 @@ export class QuantitySelectorComponent {
}
protected onIncrease(): void {
if (this.quantity() < this.max()) {
if (!this.atMaximum()) {
this.quantity.set(this.quantity() + 1);
this.increase.emit();
}