diff --git a/src/app/features/store/pages/checkout-page/checkout-page.component.html b/src/app/features/store/pages/checkout-page/checkout-page.component.html
index 8fb9344..50990be 100644
--- a/src/app/features/store/pages/checkout-page/checkout-page.component.html
+++ b/src/app/features/store/pages/checkout-page/checkout-page.component.html
@@ -5,12 +5,7 @@
} @else {
-
+
- @if (isEditingItems()) {
-
-
Terminá de modificar las cantidades para continuar con el pago.
-
- }
-
diff --git a/src/app/features/store/pages/checkout-page/checkout-page.component.scss b/src/app/features/store/pages/checkout-page/checkout-page.component.scss
index 04b30ae..33c677c 100644
--- a/src/app/features/store/pages/checkout-page/checkout-page.component.scss
+++ b/src/app/features/store/pages/checkout-page/checkout-page.component.scss
@@ -11,8 +11,7 @@
order: 1;
}
- .checkout-page__stepper-col,
- .checkout-page__editing-notice {
+ .checkout-page__stepper-col {
order: 2;
}
}
@@ -21,27 +20,6 @@
min-width: 0;
border-radius: 4px;
min-height: 420px;
-
- &--editing {
- display: none;
- }
- }
-
- &__editing-notice {
- display: grid;
- min-height: 420px;
- place-items: center;
- padding: 2rem;
- border-radius: 4px;
- background: #f5f5f5;
- color: #666666;
- text-align: center;
-
- p {
- max-width: 360px;
- margin: 0;
- font-size: 14px;
- }
}
&__cart-col {
diff --git a/src/app/features/store/pages/checkout-page/checkout-page.component.spec.ts b/src/app/features/store/pages/checkout-page/checkout-page.component.spec.ts
index c874f2e..2d312c5 100644
--- a/src/app/features/store/pages/checkout-page/checkout-page.component.spec.ts
+++ b/src/app/features/store/pages/checkout-page/checkout-page.component.spec.ts
@@ -10,9 +10,11 @@ import { AuthService } from '../../../../core/services/auth/auth.service';
import { CartService } from '../../../../core/services/cart/cart.service';
import { CheckoutService } from '../../../../core/services/checkout.service';
import { CatalogService } from '../../../../core/services/catalog/catalog.service';
+import { GlobalLoadingService } from '../../../../core/services/global-loading/global-loading.service';
import { TenantService } from '../../../../core/services/tenant.service';
import { ToastService } from '../../../../core/services/toast.service';
import { CartEditingPolicy } from '../../../../core/services/tenant.interface';
+import { ToastService } from '../../../../core/services/toast.service';
import { CheckoutPageComponent } from './checkout-page.component';
describe('CheckoutPageComponent payment validation', () => {
@@ -22,7 +24,6 @@ describe('CheckoutPageComponent payment validation', () => {
updateItemQuantity: ReturnType
;
updateItemVariant: ReturnType;
removeItem: ReturnType;
- prepareItemEditing: ReturnType;
cancelPurchase: ReturnType;
generatePaymentIntent: ReturnType;
submitPurchaseForReview: ReturnType;
@@ -37,6 +38,10 @@ describe('CheckoutPageComponent payment validation', () => {
};
let routerStub: { navigate: ReturnType };
let toastServiceStub: { danger: ReturnType };
+ let globalLoadingServiceStub: {
+ start: ReturnType;
+ stop: ReturnType;
+ };
let routeQueryParamMap: ReturnType;
let authUserState: ReturnType;
let tenantState: ReturnType<
@@ -68,7 +73,6 @@ describe('CheckoutPageComponent payment validation', () => {
updateItemQuantity: vi.fn(),
updateItemVariant: vi.fn(),
removeItem: vi.fn(),
- prepareItemEditing: vi.fn(),
cancelPurchase: vi.fn().mockResolvedValue({ status: 'cancelled' }),
generatePaymentIntent: vi.fn().mockResolvedValue({
qr_data: { qr_code: 'qr-value' },
@@ -101,6 +105,7 @@ describe('CheckoutPageComponent payment validation', () => {
});
routerStub = { navigate: vi.fn() };
toastServiceStub = { danger: vi.fn() };
+ globalLoadingServiceStub = { start: vi.fn(), stop: vi.fn() };
routeQueryParamMap = convertToParamMap({});
authUserState = signal(null);
tenantState = signal({
@@ -122,6 +127,7 @@ describe('CheckoutPageComponent payment validation', () => {
{ provide: CartService, useValue: cartServiceStub },
{ provide: TenantService, useValue: { tenant: tenantState } },
{ provide: AuthService, useValue: { user: authUserState } },
+ { provide: GlobalLoadingService, useValue: globalLoadingServiceStub },
{ provide: ToastService, useValue: toastServiceStub },
{
provide: ActivatedRoute,
@@ -477,83 +483,30 @@ describe('CheckoutPageComponent payment validation', () => {
expect(routerStub.navigate).toHaveBeenCalledWith(['/checkout/status', 25]);
});
- it('updates a purchase item while editing and refreshes checkout totals', async () => {
- const updatedPurchase = {
- id: 25,
- items: [],
- subtotal: '300.00',
- total: '300.00',
- };
- checkoutServiceStub.updateItemQuantity.mockResolvedValue(updatedPurchase);
- const { component } = createComponent();
- component.isEditingItems.set(true);
-
- await component.onPurchaseItemQuantityChange({
- item: {
- cartItemId: 91,
- imageUrl: null,
- product: 'Remera',
- originalPrice: null,
- discountedPrice: 100,
- discountPercentage: null,
- attributes: [],
- quantity: 2,
- },
- quantity: 3,
+ it('shows the API error in a toast when restoring the cart fails', async () => {
+ const message = 'La compra venció. Iniciá una nueva compra.';
+ checkoutServiceStub.cancelPurchase.mockRejectedValue({
+ error: { code: 'purchase.expired', message },
});
+ const { component } = createComponent();
- expect(checkoutServiceStub.updateItemQuantity).toHaveBeenCalledWith(
- 'tenant-test',
- 25,
- 91,
- 3,
- null,
- 'purchase',
+ await component.onModifyPurchase();
+
+ expect(toastServiceStub.danger).toHaveBeenCalledWith(message);
+ expect(routerStub.navigate).not.toHaveBeenCalled();
+ expect(globalLoadingServiceStub.start).toHaveBeenCalledOnce();
+ expect(globalLoadingServiceStub.stop).toHaveBeenCalledOnce();
+ expect(component.isRestoringCart()).toBe(false);
+ });
+
+ it('restores the previous cart and opens it when Modificar is clicked', async () => {
+ let finishNavigation!: (navigated: boolean) => void;
+ routerStub.navigate.mockReturnValue(
+ new Promise((resolve) => {
+ finishNavigation = resolve;
+ }),
);
- expect(component.createdPurchase()).toBe(updatedPurchase);
- expect(component.isUpdatingItem()).toBe(false);
- });
-
- it('keeps the payment step selected while editing and regenerates payment afterward', async () => {
- const editablePurchase = {
- id: 25,
- status: 'created',
- items: [],
- subtotal: '100.00',
- total: '100.00',
- };
- checkoutServiceStub.prepareItemEditing.mockResolvedValue(editablePurchase);
const { component } = createComponent();
- component.stepper = { currentStepIndex: signal(1) };
- const selectPaymentMethod = vi
- .spyOn(component, 'selectPaymentMethod')
- .mockResolvedValue(undefined);
-
- await component.onEditingItemsChange(true);
-
- expect(checkoutServiceStub.prepareItemEditing).toHaveBeenCalledWith('tenant-test', 25);
- expect(component.isEditingItems()).toBe(true);
- expect(component.createdPurchase()).toBe(editablePurchase);
-
- await component.onEditingItemsChange(false);
-
- expect(component.stepper.currentStepIndex()).toBe(1);
- expect(selectPaymentMethod).toHaveBeenCalledWith('qr');
- expect(component.isEditingItems()).toBe(false);
- });
-
- it('shows Modificar and returns to the home cart when checkout editing is disabled', async () => {
- tenantState.set({
- codigo: 'tenant-test',
- checkout_editing_policy: {
- code: 'disabled',
- allow_modify: false,
- allow_delete: false,
- allow_update_quantity: false,
- allow_update_variant: false,
- },
- });
- const { fixture, component } = createComponent();
component.createdPurchase.set({
id: 25,
status: 'created',
@@ -577,23 +530,28 @@ describe('CheckoutPageComponent payment validation', () => {
subtotal: '200.00',
total: '200.00',
});
- fixture.detectChanges();
+ const modification = component.onModifyPurchase();
+ await Promise.resolve();
+ await Promise.resolve();
- const modifyButton = (fixture.nativeElement as HTMLElement).querySelector('.cart-edit-btn');
- expect(modifyButton?.textContent?.trim()).toBe('Modificar');
- expect(
- (fixture.nativeElement as HTMLElement).querySelector('app-quantity-selector'),
- ).toBeNull();
-
- await component.onEditingItemsChange(true);
-
- expect(component.canUpdateCartQuantity()).toBe(false);
- expect(component.canModifyCart()).toBe(false);
- expect(component.isEditingItems()).toBe(false);
- expect(checkoutServiceStub.prepareItemEditing).not.toHaveBeenCalled();
+ expect(checkoutServiceStub.cancelPurchase).toHaveBeenCalledWith('tenant-test', 25);
+ expect(globalLoadingServiceStub.start).toHaveBeenCalledOnce();
+ expect(globalLoadingServiceStub.stop).not.toHaveBeenCalled();
+ expect(cartServiceStub.loadCart).toHaveBeenCalledOnce();
+ expect(component.createdPurchaseId()).toBeNull();
+ expect(component.createdPurchase()).toBeNull();
expect(routerStub.navigate).toHaveBeenCalledWith(['/'], {
queryParams: { openCart: true },
});
+
+ finishNavigation(true);
+ await modification;
+
+ expect(globalLoadingServiceStub.stop).toHaveBeenCalledOnce();
+
+ await component.canDeactivate();
+
+ expect(cartServiceStub.loadCart).toHaveBeenCalledOnce();
});
it('updates customer data on the existing purchase before payment', async () => {
diff --git a/src/app/features/store/pages/checkout-page/checkout-page.component.ts b/src/app/features/store/pages/checkout-page/checkout-page.component.ts
index 5524da0..aedcefa 100644
--- a/src/app/features/store/pages/checkout-page/checkout-page.component.ts
+++ b/src/app/features/store/pages/checkout-page/checkout-page.component.ts
@@ -21,8 +21,8 @@ import {
} from '../../../../core/services/checkout.service';
import { AuthService } from '../../../../core/services/auth/auth.service';
import { CartService } from '../../../../core/services/cart/cart.service';
+import { GlobalLoadingService } from '../../../../core/services/global-loading/global-loading.service';
import { ToastService } from '../../../../core/services/toast.service';
-import { BankAccount } from '../../../../core/services/tenant.interface';
import { CartComponent, CartItemMock } from '../../../../shared/components/cart/cart.component';
import { StepComponent } from '../../../../shared/components/stepper/step.component';
import { StepperComponent } from '../../../../shared/components/stepper/stepper.component';
@@ -66,6 +66,8 @@ export class CheckoutPageComponent implements OnInit, OnDestroy {
private readonly checkoutService = inject(CheckoutService);
private readonly authService = inject(AuthService);
private readonly cartService = inject(CartService);
+ private readonly globalLoadingService = inject(GlobalLoadingService);
+ private readonly toastService = inject(ToastService);
private readonly qrPollingIntervalMs = 5_000;
private readonly toastService = inject(ToastService);
private readonly qrPollingMaxAttempts = 9;
@@ -93,19 +95,6 @@ export class CheckoutPageComponent implements OnInit, OnDestroy {
protected readonly createdPurchase = signal(null);
protected readonly isLoadingPurchase = signal(true);
protected readonly checkoutStepIndex = signal(0);
- protected readonly canUpdateCartQuantity = computed(
- () => this.tenantService.tenant()?.checkout_editing_policy?.allow_update_quantity ?? false,
- );
- protected readonly canUpdateCartVariant = computed(
- () => this.tenantService.tenant()?.checkout_editing_policy?.allow_update_variant ?? false,
- );
- protected readonly canDeleteCartItems = computed(
- () => this.tenantService.tenant()?.checkout_editing_policy?.allow_delete ?? false,
- );
- protected readonly canModifyCart = computed(
- () => this.tenantService.tenant()?.checkout_editing_policy?.allow_modify ?? false,
- );
-
protected readonly cartSubtotal = computed(() => {
const purchase = this.createdPurchase();
return purchase ? parseFloat(purchase.subtotal) : 0;
@@ -134,9 +123,7 @@ export class CheckoutPageComponent implements OnInit, OnDestroy {
protected readonly transferDni = signal('');
protected readonly isUpdatingPurchase = signal(false);
- protected readonly isEditingItems = signal(false);
- protected readonly isUpdatingItem = signal(false);
- protected readonly isPreparingItemEdit = signal(false);
+ protected readonly isRestoringCart = signal(false);
protected readonly createdPurchaseId = signal(null);
protected readonly isGeneratingIntent = signal(false);
protected readonly isPaymentLoading = computed(() => this.isGeneratingIntent());
@@ -202,187 +189,39 @@ export class CheckoutPageComponent implements OnInit, OnDestroy {
};
}
- protected async onEditingItemsChange(editing: boolean): Promise {
- if (this.isUpdatingItem() || this.isPreparingItemEdit()) {
+ protected async onModifyPurchase(): Promise {
+ if (this.isRestoringCart()) {
return;
}
+<<<<<<< HEAD
if (editing && !this.canModifyCart()) {
const tenant = this.tenantService.tenant();
- const purchaseId = this.createdPurchaseId();
-
- if (!tenant || !purchaseId) {
- return;
- }
-
- this.isPreparingItemEdit.set(true);
- try {
- await this.checkoutService.cancelPurchase(tenant.codigo, purchaseId);
- this.createdPurchaseId.set(null);
- this.createdPurchase.set(null);
- await firstValueFrom(this.cartService.loadCart());
- await this.router.navigate(['/'], {
- queryParams: { openCart: true },
- });
- } catch (error) {
- this.handleCheckoutError(error, 'No se pudo restaurar el carrito para editarlo.');
- } finally {
- this.isPreparingItemEdit.set(false);
- }
-
return;
}
- if (!editing) {
- this.isEditingItems.set(false);
-
- if (this.stepper?.currentStepIndex() === 1) {
- void this.selectPaymentMethod(this.selectedPaymentMethod());
- }
-
- return;
- }
-
- this.isEditingItems.set(true);
- this.stopQrPolling();
- this.stopTransferPolling();
- this.qrData.set(null);
- this.qrPaymentStatus.set('idle');
- this.transferAccount.set(null);
- this.transferValidationStatus.set('idle');
-
- const tenant = this.tenantService.tenant();
- const purchaseId = this.createdPurchaseId();
-
- if (!tenant || !purchaseId) {
- this.isEditingItems.set(false);
- return;
- }
-
- this.isPreparingItemEdit.set(true);
+ this.isRestoringCart.set(true);
+ this.globalLoadingService.start();
try {
- const purchase = await this.checkoutService.prepareItemEditing(tenant.codigo, purchaseId);
- this.createdPurchase.set(purchase);
+ await this.checkoutService.cancelPurchase(tenant.codigo, purchaseId);
+ this.createdPurchaseId.set(null);
+ this.createdPurchase.set(null);
+ await firstValueFrom(this.cartService.loadCart());
+ this.navigationStarted = true;
+ await this.router.navigate(['/'], {
+ queryParams: { openCart: true },
+ });
} catch (error) {
- this.handleCheckoutError(error, 'No se pudo preparar la compra para editarla.');
- this.isEditingItems.set(false);
+ console.error('Failed to restore cart for editing:', error);
+ this.showRequestError(error, 'No se pudo recuperar el carrito para modificar la compra.');
} finally {
- this.isPreparingItemEdit.set(false);
+ this.globalLoadingService.stop();
+ this.isRestoringCart.set(false);
+>>>>>>> feature/restore-cart-on-modify
}
}
-
- protected async onPurchaseItemQuantityChange(event: {
- item: CartItemMock;
- quantity: number;
- }): Promise {
- const tenant = this.tenantService.tenant();
- const purchaseId = this.createdPurchaseId();
- const itemId = event.item.cartItemId;
-
- if (
- !this.canUpdateCartQuantity() ||
- !tenant ||
- !purchaseId ||
- !itemId ||
- this.isUpdatingItem() ||
- !this.isEditingItems()
- ) {
- return;
- }
-
- this.isUpdatingItem.set(true);
- try {
- const purchase = await this.checkoutService.updateItemQuantity(
- tenant.codigo,
- purchaseId,
- itemId,
- event.quantity,
- this.createdPurchase()?.cart_id ?? null,
- this.createdPurchase()?.items_source ?? 'purchase',
- );
- this.createdPurchase.set(purchase);
- } catch (error) {
- this.handleCheckoutError(error, 'No se pudo actualizar la cantidad del producto.');
- } finally {
- this.isUpdatingItem.set(false);
- }
- }
-
- protected async onPurchaseItemVariantChange(event: {
- item: CartItemMock;
- variantId: number;
- }): Promise {
- const tenant = this.tenantService.tenant();
- const purchase = this.createdPurchase();
- const itemId = event.item.cartItemId;
-
- if (
- !this.canUpdateCartVariant() ||
- !tenant ||
- !purchase ||
- !itemId ||
- this.isUpdatingItem() ||
- !this.isEditingItems()
- ) {
- return;
- }
-
- this.isUpdatingItem.set(true);
- try {
- this.createdPurchase.set(
- await this.checkoutService.updateItemVariant(
- tenant.codigo,
- purchase.id,
- itemId,
- event.variantId,
- event.item.quantity,
- purchase.cart_id,
- purchase.items_source,
- ),
- );
- } catch (error) {
- this.handleCheckoutError(error, 'No se pudo actualizar la variante del producto.');
- } finally {
- this.isUpdatingItem.set(false);
- }
- }
-
- protected async onPurchaseItemRemove(event: { item: CartItemMock }): Promise {
- const tenant = this.tenantService.tenant();
- const purchase = this.createdPurchase();
- const itemId = event.item.cartItemId;
-
- if (
- !this.canDeleteCartItems() ||
- !tenant ||
- !purchase ||
- !itemId ||
- this.isUpdatingItem() ||
- !this.isEditingItems()
- ) {
- return;
- }
-
- this.isUpdatingItem.set(true);
- try {
- this.createdPurchase.set(
- await this.checkoutService.removeItem(
- tenant.codigo,
- purchase.id,
- itemId,
- purchase.cart_id,
- purchase.items_source,
- ),
- );
- } catch (error) {
- this.handleCheckoutError(error, 'No se pudo eliminar el producto de la compra.');
- } finally {
- this.isUpdatingItem.set(false);
- }
- }
-
protected async onStep1Continue(): Promise {
- if (this.form.invalid || this.isUpdatingPurchase() || this.isEditingItems()) return;
+ if (this.form.invalid || this.isUpdatingPurchase()) return;
const tenant = this.tenantService.tenant();
const purchaseId = this.createdPurchaseId();
@@ -405,7 +244,8 @@ export class CheckoutPageComponent implements OnInit, OnDestroy {
// Auto trigger intent for default option
void this.selectPaymentMethod(this.selectedPaymentMethod());
} catch (error) {
- this.handleCheckoutError(error, 'No se pudieron actualizar los datos de la compra.');
+ console.error('Failed to create purchase:', error);
+ this.showRequestError(error, 'No se pudieron actualizar los datos de la compra.');
} finally {
this.isUpdatingPurchase.set(false);
}
@@ -431,13 +271,14 @@ export class CheckoutPageComponent implements OnInit, OnDestroy {
await firstValueFrom(this.cartService.loadCart());
} catch (error) {
console.error('Failed to load the active cart after leaving checkout:', error);
+ this.showRequestError(error, 'No se pudo cargar el carrito.');
}
return true;
}
protected async selectPaymentMethod(method: PaymentMethod): Promise {
- if (this.navigationStarted || this.isEditingItems()) {
+ if (this.navigationStarted) {
return;
}
@@ -481,17 +322,14 @@ export class CheckoutPageComponent implements OnInit, OnDestroy {
this.startQrPolling();
}
} catch (error) {
- this.handleCheckoutError(error, 'No se pudo generar la intenci\u00f3n de pago.');
+ console.error('Failed to generate payment intent:', error);
+ this.showRequestError(error, 'No se pudo generar el pago.');
} finally {
this.isGeneratingIntent.set(false);
}
}
protected async generateTransferIntent(dni: string): Promise {
- if (this.isEditingItems()) {
- return;
- }
-
const purchaseId = this.createdPurchaseId();
const tenant = this.tenantService.tenant();
@@ -518,7 +356,8 @@ export class CheckoutPageComponent implements OnInit, OnDestroy {
});
}
} catch (error) {
- this.handleCheckoutError(error, 'No se pudo generar la intenci\u00f3n de transferencia.');
+ console.error('Failed to generate transfer payment intent:', error);
+ this.showRequestError(error, 'No se pudo generar el pago por transferencia.');
} finally {
this.isGeneratingIntent.set(false);
}
@@ -549,7 +388,6 @@ export class CheckoutPageComponent implements OnInit, OnDestroy {
!purchaseId ||
!tenant ||
this.navigationStarted ||
- this.isEditingItems() ||
this.transferValidationStatus() === 'checking'
) {
return;
@@ -571,16 +409,10 @@ export class CheckoutPageComponent implements OnInit, OnDestroy {
}
if (purchase.status === 'paid') {
- this.navigateToPurchaseStatus(purchaseId);
- return;
- }
this.scheduleTransferPoll(runId);
- } catch (error) {
- const expired = this.handleCheckoutError(
- error,
- 'No se pudo enviar la compra a revisi\u00f3n.',
- );
+ console.error('Failed to submit transfer payment for review:', error);
+ this.showRequestError(error, 'No se pudo enviar el pago para su validación.');
if (!expired && runId === this.transferPollingRunId) {
this.transferValidationStatus.set('error');
@@ -811,57 +643,21 @@ export class CheckoutPageComponent implements OnInit, OnDestroy {
}
} catch (error) {
console.error('Failed to load purchase:', error);
+ this.showRequestError(error, 'No se pudo cargar la compra.');
void this.router.navigate(['/']);
}
}
- private handleCheckoutError(error: unknown, fallbackMessage: string): boolean {
- if (!(error instanceof HttpErrorResponse)) {
- this.toastService.danger(fallbackMessage);
+ private showRequestError(error: unknown, fallbackMessage: string): void {
+ const payload =
+ typeof error === 'object' && error !== null && 'error' in error
+ ? (error as { error?: { message?: unknown } }).error
+ : undefined;
+ const message =
+ typeof payload?.message === 'string' && payload.message.trim()
+ ? payload.message
+ : fallbackMessage;
- return false;
- }
-
- const response = error.error as ApiErrorResponse | null;
-
- if (response?.code === 'purchase.expired' || this.hasExpiredPurchase()) {
- this.navigationStarted = true;
- this.stopQrPolling();
- this.stopTransferPolling();
- this.toastService.danger(
- response?.code === 'purchase.expired' && response.message
- ? response.message
- : 'La compra venci\u00f3. Inici\u00e1 una nueva compra.',
- );
- void this.router.navigate(['/']);
-
- return true;
- }
-
- const validationMessage = response?.errors
- ? Object.values(response.errors).flat().find(Boolean)
- : undefined;
-
- this.toastService.danger(validationMessage ?? response?.message ?? fallbackMessage);
-
- return false;
- }
-
- private hasExpiredPurchase(): boolean {
- const purchase = this.createdPurchase();
-
- if (!purchase) {
- return false;
- }
-
- if (purchase.status === 'expired') {
- return true;
- }
-
- if (!purchase.expires_at) {
- return false;
- }
-
- return Date.parse(purchase.expires_at) <= Date.now();
+ this.toastService.danger(message);
}
}
diff --git a/src/app/shared/components/cart/cart.component.html b/src/app/shared/components/cart/cart.component.html
index 715f1de..66dab93 100644
--- a/src/app/shared/components/cart/cart.component.html
+++ b/src/app/shared/components/cart/cart.component.html
@@ -9,17 +9,17 @@
@if (
allowModify() &&
(!readonly() || showModifyWhenReadonly()) &&
- requireEditingMode() &&
+ (requireEditingMode() || modifyAsAction()) &&
items().length > 0
) {
}
diff --git a/src/app/shared/components/cart/cart.component.spec.ts b/src/app/shared/components/cart/cart.component.spec.ts
index 29dc1ad..7aaf644 100644
--- a/src/app/shared/components/cart/cart.component.spec.ts
+++ b/src/app/shared/components/cart/cart.component.spec.ts
@@ -367,6 +367,57 @@ describe('CartComponent', () => {
expect(editingChange).toHaveBeenLastCalledWith(false);
});
+ it('emits Modificar as an action without toggling to Listo', async () => {
+ await TestBed.configureTestingModule({
+ imports: [CartComponent],
+ providers: [
+ {
+ provide: CartService,
+ useValue: {
+ cart: signal(null).asReadonly(),
+ updateItemQuantity: vi.fn(),
+ removeItem: vi.fn(),
+ },
+ },
+ { provide: ModalService, useValue: {} },
+ {
+ provide: ToastService,
+ useValue: { success: vi.fn(), info: vi.fn(), danger: vi.fn() },
+ },
+ ],
+ }).compileComponents();
+
+ const fixture = TestBed.createComponent(CartComponent);
+ fixture.componentRef.setInput('items', [
+ {
+ cartItemId: 10,
+ imageUrl: null,
+ product: 'Producto de prueba',
+ originalPrice: null,
+ discountedPrice: 1000,
+ discountPercentage: null,
+ attributes: [],
+ quantity: 1,
+ },
+ ]);
+ fixture.componentRef.setInput('readonly', true);
+ fixture.componentRef.setInput('showModifyWhenReadonly', true);
+ fixture.componentRef.setInput('modifyAsAction', true);
+ const modify = vi.fn();
+ fixture.componentInstance.modify.subscribe(modify);
+ fixture.detectChanges();
+
+ const modifyButton = fixture.debugElement.query(By.css('.cart-edit-btn'));
+ expect(modifyButton.nativeElement.textContent.trim()).toBe('Modificar');
+
+ modifyButton.nativeElement.click();
+ fixture.detectChanges();
+
+ expect(modify).toHaveBeenCalledOnce();
+ expect(modifyButton.nativeElement.textContent.trim()).toBe('Modificar');
+ expect(fixture.componentInstance.editing()).toBe(false);
+ });
+
it('allows editing directly when the optional Modificar toggle is disabled', async () => {
await TestBed.configureTestingModule({
imports: [CartComponent],
diff --git a/src/app/shared/components/cart/cart.component.ts b/src/app/shared/components/cart/cart.component.ts
index 6b907ff..bcd1bb1 100644
--- a/src/app/shared/components/cart/cart.component.ts
+++ b/src/app/shared/components/cart/cart.component.ts
@@ -56,6 +56,7 @@ export class CartComponent {
readonly allowUpdateQuantity = input(true);
readonly allowModify = input(true);
readonly showModifyWhenReadonly = input(false);
+ readonly modifyAsAction = input(false);
readonly requireEditingMode = input(false);
readonly allowUpdateVariant = input(true);
readonly allowDelete = input(true);
@@ -66,6 +67,7 @@ export class CartComponent {
readonly editing = model(false);
readonly closed = output();
+ readonly modify = output();
readonly itemQuantityChange = output<{
item: CartItemMock;
index: number;
@@ -257,6 +259,11 @@ export class CartComponent {
return;
}
+ if (this.modifyAsAction()) {
+ this.modify.emit();
+ return;
+ }
+
const editing = !this.editing();
this.editing.set(editing);
}