test(catalog): cover availability-driven controls
This commit is contained in:
@@ -2,6 +2,7 @@ import { TestBed } from '@angular/core/testing';
|
||||
import { beforeEach, describe, expect, it } from 'vitest';
|
||||
|
||||
import { ProductAttribute } from '../../../../core/services/catalog/catalog.interface';
|
||||
import { createCatalogAvailability } from '../../../../core/services/catalog/catalog-availability';
|
||||
import { ProductAttributeSelectorComponent } from './product-attribute-selector.component';
|
||||
|
||||
describe('ProductAttributeSelectorComponent', () => {
|
||||
@@ -31,7 +32,7 @@ describe('ProductAttributeSelectorComponent', () => {
|
||||
fixture.componentRef.setInput('variants', [
|
||||
{
|
||||
id: 1,
|
||||
maximum_addable_quantity: null,
|
||||
availability: createCatalogAvailability(null),
|
||||
values: { size: 'S' },
|
||||
},
|
||||
]);
|
||||
@@ -51,12 +52,12 @@ describe('ProductAttributeSelectorComponent', () => {
|
||||
fixture.componentRef.setInput('variants', [
|
||||
{
|
||||
id: 1,
|
||||
maximum_addable_quantity: 0,
|
||||
availability: createCatalogAvailability(0),
|
||||
values: { size: 'S' },
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
maximum_addable_quantity: 2,
|
||||
availability: createCatalogAvailability(2),
|
||||
values: { size: 'M' },
|
||||
},
|
||||
]);
|
||||
@@ -92,9 +93,9 @@ describe('ProductAttributeSelectorComponent', () => {
|
||||
]);
|
||||
fixture.componentRef.setInput('inventoryPolicy', 'unlimited');
|
||||
fixture.componentRef.setInput('variants', [
|
||||
{ id: 1, maximum_addable_quantity: null, values: { event_date: '1' } },
|
||||
{ id: 2, maximum_addable_quantity: null, values: { event_date: '2' } },
|
||||
{ id: 3, maximum_addable_quantity: null, values: { event_date: ['1', '2'] } },
|
||||
{ id: 1, availability: createCatalogAvailability(null), values: { event_date: '1' } },
|
||||
{ id: 2, availability: createCatalogAvailability(null), values: { event_date: '2' } },
|
||||
{ id: 3, availability: createCatalogAvailability(null), values: { event_date: ['1', '2'] } },
|
||||
]);
|
||||
fixture.detectChanges();
|
||||
|
||||
@@ -135,8 +136,16 @@ describe('ProductAttributeSelectorComponent', () => {
|
||||
]);
|
||||
fixture.componentRef.setInput('inventoryPolicy', 'unlimited');
|
||||
fixture.componentRef.setInput('variants', [
|
||||
{ id: 1, maximum_addable_quantity: null, values: { size: 'S', internal_type: 'adult' } },
|
||||
{ id: 2, maximum_addable_quantity: null, values: { size: 'M', internal_type: 'child' } },
|
||||
{
|
||||
id: 1,
|
||||
availability: createCatalogAvailability(null),
|
||||
values: { size: 'S', internal_type: 'adult' },
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
availability: createCatalogAvailability(null),
|
||||
values: { size: 'M', internal_type: 'child' },
|
||||
},
|
||||
]);
|
||||
fixture.detectChanges();
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { CatalogItemDetail } from '../../../../core/services/catalog/catalog.interface';
|
||||
import { CatalogService } from '../../../../core/services/catalog/catalog.service';
|
||||
import { CatalogAvailabilityService } from '../../../../core/services/catalog/catalog-availability.service';
|
||||
import { createCatalogAvailability } from '../../../../core/services/catalog/catalog-availability';
|
||||
import { CartService } from '../../../../core/services/cart/cart.service';
|
||||
import { ToastService } from '../../../../core/services/toast.service';
|
||||
import { ProductDetailPageComponent } from './product-detail-page.component';
|
||||
@@ -38,7 +39,7 @@ describe('ProductDetailPageComponent', () => {
|
||||
has_tickets: false,
|
||||
minimum_use_date: null,
|
||||
maximum_use_date: null,
|
||||
maximum_addable_quantity: 10,
|
||||
availability: createCatalogAvailability(10),
|
||||
attributes: [],
|
||||
variants: [],
|
||||
};
|
||||
@@ -179,7 +180,7 @@ describe('ProductDetailPageComponent', () => {
|
||||
|
||||
it('reloads product availability when the cart changes', async () => {
|
||||
catalogServiceStub.getCatalogItem.mockReturnValue(
|
||||
of({ ...mockProduct, maximum_addable_quantity: 4 }),
|
||||
of({ ...mockProduct, availability: createCatalogAvailability(4) }),
|
||||
);
|
||||
await configureTestingModule();
|
||||
const fixture = TestBed.createComponent(ProductDetailPageComponent);
|
||||
@@ -207,10 +208,10 @@ describe('ProductDetailPageComponent', () => {
|
||||
const detailProduct: CatalogItemDetail = {
|
||||
...mockProduct,
|
||||
images: ['https://example.com/product.png'],
|
||||
variants: [{ id: 123, maximum_addable_quantity: 10, values: {} }],
|
||||
variants: [{ id: 123, availability: createCatalogAvailability(10), values: {} }],
|
||||
selected_variant: {
|
||||
id: 123,
|
||||
maximum_addable_quantity: 10,
|
||||
availability: createCatalogAvailability(10),
|
||||
images: ['https://example.com/variant1.png', 'https://example.com/variant2.png'],
|
||||
values: {},
|
||||
},
|
||||
@@ -312,11 +313,15 @@ describe('ProductDetailPageComponent', () => {
|
||||
},
|
||||
],
|
||||
variants: [
|
||||
{ id: 123, maximum_addable_quantity: 10, values: { color: 'beige', material: 'Cuero' } },
|
||||
{
|
||||
id: 123,
|
||||
availability: createCatalogAvailability(10),
|
||||
values: { color: 'beige', material: 'Cuero' },
|
||||
},
|
||||
],
|
||||
selected_variant: {
|
||||
id: 123,
|
||||
maximum_addable_quantity: 10,
|
||||
availability: createCatalogAvailability(10),
|
||||
images: ['https://example.com/variant1.png'],
|
||||
values: {
|
||||
color: 'beige',
|
||||
@@ -353,8 +358,18 @@ describe('ProductDetailPageComponent', () => {
|
||||
purpose: 'entry',
|
||||
has_tickets: true,
|
||||
variants: [
|
||||
{ id: 101, event_date_id: 20, maximum_addable_quantity: 10, values: { event_date: '20' } },
|
||||
{ id: 102, event_date_id: 21, maximum_addable_quantity: 10, values: { event_date: '21' } },
|
||||
{
|
||||
id: 101,
|
||||
event_date_id: 20,
|
||||
availability: createCatalogAvailability(10),
|
||||
values: { event_date: '20' },
|
||||
},
|
||||
{
|
||||
id: 102,
|
||||
event_date_id: 21,
|
||||
availability: createCatalogAvailability(10),
|
||||
values: { event_date: '21' },
|
||||
},
|
||||
],
|
||||
attributes: [
|
||||
{
|
||||
@@ -385,7 +400,7 @@ describe('ProductDetailPageComponent', () => {
|
||||
selected_variant: {
|
||||
id: 101,
|
||||
event_date_id: 20,
|
||||
maximum_addable_quantity: 10,
|
||||
availability: createCatalogAvailability(10),
|
||||
images: [],
|
||||
values: {},
|
||||
},
|
||||
@@ -407,6 +422,8 @@ describe('ProductDetailPageComponent', () => {
|
||||
|
||||
options[1].click();
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(catalogServiceStub.getCatalogItem).toHaveBeenCalledWith(1, 102);
|
||||
});
|
||||
@@ -429,7 +446,7 @@ describe('ProductDetailPageComponent', () => {
|
||||
|
||||
fixture.componentInstance['selectedVariant'].set({
|
||||
id: 1,
|
||||
maximum_addable_quantity: 10,
|
||||
availability: createCatalogAvailability(10),
|
||||
values: {},
|
||||
});
|
||||
fixture.detectChanges();
|
||||
@@ -519,7 +536,7 @@ describe('ProductDetailPageComponent', () => {
|
||||
error: {
|
||||
code: 'purchase.limit_exceeded',
|
||||
message: 'Podés agregar hasta 2 unidades más de “Auriculares Bluetooth”.',
|
||||
maximum_addable_quantity: 2,
|
||||
availability: createCatalogAvailability(2),
|
||||
},
|
||||
}),
|
||||
);
|
||||
@@ -544,13 +561,13 @@ describe('ProductDetailPageComponent', () => {
|
||||
variants: [
|
||||
{
|
||||
id: 123,
|
||||
maximum_addable_quantity: 5,
|
||||
availability: createCatalogAvailability(5),
|
||||
values: {},
|
||||
},
|
||||
],
|
||||
selected_variant: {
|
||||
id: 123,
|
||||
maximum_addable_quantity: 5,
|
||||
availability: createCatalogAvailability(5),
|
||||
images: [],
|
||||
values: {},
|
||||
},
|
||||
@@ -583,7 +600,7 @@ describe('ProductDetailPageComponent', () => {
|
||||
resolveProduct({
|
||||
...mockProduct,
|
||||
variants: [],
|
||||
maximum_addable_quantity: 4,
|
||||
availability: createCatalogAvailability(4),
|
||||
});
|
||||
|
||||
await configureTestingModule();
|
||||
@@ -609,13 +626,13 @@ describe('ProductDetailPageComponent', () => {
|
||||
variants: [
|
||||
{
|
||||
id: 123,
|
||||
maximum_addable_quantity: 5,
|
||||
availability: createCatalogAvailability(5),
|
||||
values: {},
|
||||
},
|
||||
],
|
||||
selected_variant: {
|
||||
id: 123,
|
||||
maximum_addable_quantity: 5,
|
||||
availability: createCatalogAvailability(5),
|
||||
images: [],
|
||||
values: {},
|
||||
},
|
||||
@@ -647,7 +664,7 @@ describe('ProductDetailPageComponent', () => {
|
||||
it('allows unlimited variants to increase quantity without a maximum', async () => {
|
||||
const unlimitedVariant = {
|
||||
id: 321,
|
||||
maximum_addable_quantity: null,
|
||||
availability: createCatalogAvailability(null),
|
||||
values: {},
|
||||
};
|
||||
resolveProduct({
|
||||
@@ -655,7 +672,7 @@ describe('ProductDetailPageComponent', () => {
|
||||
inventory_policy: 'unlimited',
|
||||
selected_variant: {
|
||||
id: 321,
|
||||
maximum_addable_quantity: null,
|
||||
availability: createCatalogAvailability(null),
|
||||
images: [],
|
||||
values: {},
|
||||
},
|
||||
@@ -679,7 +696,7 @@ describe('ProductDetailPageComponent', () => {
|
||||
it('caps an unlimited variant at the per-user purchase limit', async () => {
|
||||
const unlimitedVariant = {
|
||||
id: 322,
|
||||
maximum_addable_quantity: 2,
|
||||
availability: createCatalogAvailability(2),
|
||||
values: {},
|
||||
};
|
||||
resolveProduct({
|
||||
@@ -688,7 +705,7 @@ describe('ProductDetailPageComponent', () => {
|
||||
max_units_per_user: 2,
|
||||
selected_variant: {
|
||||
id: 322,
|
||||
maximum_addable_quantity: 2,
|
||||
availability: createCatalogAvailability(2),
|
||||
images: [],
|
||||
values: { event_date: '20' },
|
||||
},
|
||||
@@ -712,14 +729,14 @@ describe('ProductDetailPageComponent', () => {
|
||||
it('disables purchase actions for tracked variants without stock', async () => {
|
||||
const trackedVariant = {
|
||||
id: 654,
|
||||
maximum_addable_quantity: 0,
|
||||
availability: createCatalogAvailability(0),
|
||||
values: {},
|
||||
};
|
||||
resolveProduct({
|
||||
...mockProduct,
|
||||
selected_variant: {
|
||||
id: 654,
|
||||
maximum_addable_quantity: 0,
|
||||
availability: createCatalogAvailability(0),
|
||||
images: [],
|
||||
values: {},
|
||||
},
|
||||
|
||||
@@ -5,6 +5,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { CatalogItemDetail } from '../../../../core/services/catalog/catalog.interface';
|
||||
import { CatalogService } from '../../../../core/services/catalog/catalog.service';
|
||||
import { createCatalogAvailability } from '../../../../core/services/catalog/catalog-availability';
|
||||
import {
|
||||
PRODUCT_DETAIL_ERROR_MESSAGE,
|
||||
PRODUCT_DETAIL_INVALID_ID_MESSAGE,
|
||||
@@ -29,7 +30,7 @@ describe('productDetailResolver', () => {
|
||||
has_tickets: false,
|
||||
minimum_use_date: null,
|
||||
maximum_use_date: null,
|
||||
maximum_addable_quantity: 0,
|
||||
availability: createCatalogAvailability(0),
|
||||
attributes: [],
|
||||
variants: [],
|
||||
};
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
} from '../../../../core/services/catalog/catalog.interface';
|
||||
import { CatalogService } from '../../../../core/services/catalog/catalog.service';
|
||||
import { CatalogAvailabilityService } from '../../../../core/services/catalog/catalog-availability.service';
|
||||
import { createCatalogAvailability } from '../../../../core/services/catalog/catalog-availability';
|
||||
import { Tenant } from '../../../../core/services/tenant.interface';
|
||||
import { TenantService } from '../../../../core/services/tenant.service';
|
||||
import { ToastService } from '../../../../core/services/toast.service';
|
||||
@@ -83,6 +84,7 @@ const pageOneItems: CatalogFeaturedItem[] = [
|
||||
nombre: 'Auriculares Bluetooth',
|
||||
precio: '24999.00',
|
||||
image: '/catalog/auriculares.jpg',
|
||||
availability: createCatalogAvailability(null),
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
@@ -90,6 +92,7 @@ const pageOneItems: CatalogFeaturedItem[] = [
|
||||
nombre: 'Teclado Mecanico',
|
||||
precio: '18999.00',
|
||||
image: null,
|
||||
availability: createCatalogAvailability(null),
|
||||
},
|
||||
];
|
||||
|
||||
@@ -406,7 +409,14 @@ describe('StoreHomePageComponent', () => {
|
||||
|
||||
it('requests another page for the selected featured group', async () => {
|
||||
const pageTwoItems: CatalogFeaturedItem[] = [
|
||||
{ id: 3, type: 'product', nombre: 'Mouse Gamer', precio: '15999.00', image: null },
|
||||
{
|
||||
id: 3,
|
||||
type: 'product',
|
||||
nombre: 'Mouse Gamer',
|
||||
precio: '15999.00',
|
||||
image: null,
|
||||
availability: createCatalogAvailability(null),
|
||||
},
|
||||
];
|
||||
const catalogServiceStub = {
|
||||
getCatalog: vi.fn(),
|
||||
|
||||
@@ -6,6 +6,7 @@ import { of } from 'rxjs';
|
||||
import { ApiPaginatedResponse } from '../../../core/services/api-paginated-response.interface';
|
||||
import { CartService } from '../../../core/services/cart/cart.service';
|
||||
import { CatalogService } from '../../../core/services/catalog/catalog.service';
|
||||
import { createCatalogAvailability } from '../../../core/services/catalog/catalog-availability';
|
||||
import {
|
||||
CatalogFeaturedItems,
|
||||
CatalogGroupLayout,
|
||||
@@ -23,6 +24,7 @@ describe('ProductListComponent', () => {
|
||||
descripcion: 'Primera descripcion',
|
||||
precio: '100.00',
|
||||
image: '/images/one.png',
|
||||
availability: createCatalogAvailability(null),
|
||||
variants: [],
|
||||
},
|
||||
{
|
||||
@@ -32,6 +34,7 @@ describe('ProductListComponent', () => {
|
||||
descripcion: 'Segunda descripcion',
|
||||
precio: 200,
|
||||
image: null,
|
||||
availability: createCatalogAvailability(null),
|
||||
variants: [],
|
||||
},
|
||||
];
|
||||
@@ -193,8 +196,8 @@ describe('ProductListComponent', () => {
|
||||
const itemWithVariants: ProductListItem = {
|
||||
...items[0],
|
||||
variants: [
|
||||
{ id: 91, maximum_addable_quantity: 3, values: { fecha: '10 de octubre' } },
|
||||
{ id: 92, maximum_addable_quantity: 4, values: { fecha: '11 de octubre' } },
|
||||
{ id: 91, availability: createCatalogAvailability(3), values: { fecha: '10 de octubre' } },
|
||||
{ id: 92, availability: createCatalogAvailability(4), values: { fecha: '11 de octubre' } },
|
||||
],
|
||||
};
|
||||
const fixture = await render('column_with_cart', [itemWithVariants]);
|
||||
@@ -220,7 +223,7 @@ describe('ProductListComponent', () => {
|
||||
variants: [
|
||||
{
|
||||
id: 91,
|
||||
maximum_addable_quantity: 2,
|
||||
availability: createCatalogAvailability(2),
|
||||
values: { fecha: '10 de octubre' },
|
||||
},
|
||||
],
|
||||
@@ -248,7 +251,7 @@ describe('ProductListComponent', () => {
|
||||
variants: [
|
||||
{
|
||||
id: 91,
|
||||
maximum_addable_quantity: 0,
|
||||
availability: createCatalogAvailability(0),
|
||||
values: { fecha: '10 de octubre' },
|
||||
},
|
||||
],
|
||||
@@ -304,7 +307,7 @@ describe('ProductListComponent', () => {
|
||||
{
|
||||
id: 401,
|
||||
precio: '10000.00',
|
||||
maximum_addable_quantity: 1,
|
||||
availability: createCatalogAvailability(1),
|
||||
values: {
|
||||
tipo: { value: 'vip', label: 'VIP' },
|
||||
sector: { value: 'a', label: 'Sector A' },
|
||||
@@ -315,7 +318,7 @@ describe('ProductListComponent', () => {
|
||||
{
|
||||
id: 402,
|
||||
precio: '12000.00',
|
||||
maximum_addable_quantity: 1,
|
||||
availability: createCatalogAvailability(1),
|
||||
values: {
|
||||
tipo: { value: 'vip', label: 'VIP' },
|
||||
sector: { value: 'a', label: 'Sector A' },
|
||||
|
||||
@@ -58,11 +58,22 @@ describe('ProductVerticalWithCartCardComponent', () => {
|
||||
|
||||
it('shows the backend availability message with the reusable tooltip', async () => {
|
||||
const fixture = await createComponent();
|
||||
fixture.componentRef.setInput('maximumAddableQuantity', 0);
|
||||
fixture.componentRef.setInput(
|
||||
'unavailableMessage',
|
||||
'Alcanzaste el cupo máximo permitido para este producto.',
|
||||
);
|
||||
fixture.componentRef.setInput('availability', {
|
||||
maximum_quantity: 0,
|
||||
restrictions: [
|
||||
{
|
||||
code: 'user_quota_reached',
|
||||
message: 'Alcanzaste el cupo máximo permitido para este producto.',
|
||||
scope: 'product',
|
||||
},
|
||||
],
|
||||
capabilities: {
|
||||
select_variant: false,
|
||||
change_quantity: false,
|
||||
add_to_cart: false,
|
||||
buy_now: false,
|
||||
},
|
||||
});
|
||||
fixture.detectChanges();
|
||||
|
||||
const tooltip = (fixture.nativeElement as HTMLElement).querySelector('app-tooltip');
|
||||
|
||||
@@ -62,6 +62,8 @@ describe('VariantSelectorComponent', () => {
|
||||
]);
|
||||
fixture.componentRef.setInput('selectedVariant', 2);
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
fixture.detectChanges();
|
||||
|
||||
const selects = Array.from(
|
||||
fixture.nativeElement.querySelectorAll('select'),
|
||||
@@ -80,6 +82,28 @@ describe('VariantSelectorComponent', () => {
|
||||
expect(fixture.componentInstance.selectedVariant()).toBe(1);
|
||||
});
|
||||
|
||||
it('does not offer variants whose availability forbids selection', async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [VariantSelectorComponent],
|
||||
}).compileComponents();
|
||||
const fixture = TestBed.createComponent(VariantSelectorComponent);
|
||||
fixture.componentRef.setInput('variants', [
|
||||
{ id: 1, values: { talle: 'S' } },
|
||||
{
|
||||
id: 2,
|
||||
values: { talle: 'M' },
|
||||
availability: { capabilities: { select_variant: false } },
|
||||
},
|
||||
]);
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(fixture.nativeElement.textContent).toContain('S');
|
||||
expect(fixture.nativeElement.textContent).not.toContain('M');
|
||||
expect(fixture.componentInstance.selectedVariant()).toBe(1);
|
||||
});
|
||||
|
||||
it('requires manual selections when autoSelectFirst is disabled', async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [VariantSelectorComponent],
|
||||
|
||||
Reference in New Issue
Block a user