test: add unit tests for frontend top-down auto-reset and backend split logic
This commit is contained in:
42
shop/lib/checkout-split.test.ts
Normal file
42
shop/lib/checkout-split.test.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { splitCart, CartItem } from './checkout-split';
|
||||
|
||||
describe('Checkout Split Logic', () => {
|
||||
it('should place purchase items only into purchase group', () => {
|
||||
const cart: CartItem[] = [
|
||||
{ billingInterval: 'one_time', selections: {} },
|
||||
{ billingType: 'purchase', selections: {} }
|
||||
];
|
||||
|
||||
const { purchaseItems, subscriptionItems } = splitCart(cart);
|
||||
|
||||
expect(purchaseItems.length).toBe(2);
|
||||
expect(subscriptionItems.length).toBe(0);
|
||||
});
|
||||
|
||||
it('should place subscription items only into subscription group', () => {
|
||||
const cart: CartItem[] = [
|
||||
{ billingInterval: 'monthly', selections: {} },
|
||||
{ billingType: 'subscription', selections: {} }
|
||||
];
|
||||
|
||||
const { purchaseItems, subscriptionItems } = splitCart(cart);
|
||||
|
||||
expect(purchaseItems.length).toBe(0);
|
||||
expect(subscriptionItems.length).toBe(2);
|
||||
});
|
||||
|
||||
it('should split mixed cart into separate purchase and subscription groups', () => {
|
||||
const cart: CartItem[] = [
|
||||
{ billingInterval: 'one_time', selections: {} },
|
||||
{ billingInterval: 'monthly', selections: {} },
|
||||
{ billingType: 'purchase', selections: {} },
|
||||
{ billingType: 'subscription', selections: {} }
|
||||
];
|
||||
|
||||
const { purchaseItems, subscriptionItems } = splitCart(cart);
|
||||
|
||||
expect(purchaseItems.length).toBe(2);
|
||||
expect(subscriptionItems.length).toBe(2);
|
||||
});
|
||||
});
|
||||
17
shop/lib/checkout-split.ts
Normal file
17
shop/lib/checkout-split.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
export interface CartItem {
|
||||
id?: string;
|
||||
billingInterval?: 'one_time' | 'monthly';
|
||||
billingType?: 'purchase' | 'subscription';
|
||||
selections: any;
|
||||
moduleQuantities?: Record<string, number>;
|
||||
}
|
||||
|
||||
export function splitCart(items: CartItem[]) {
|
||||
const purchaseItems = items.filter(
|
||||
item => item.billingInterval === 'one_time' || item.billingType === 'purchase'
|
||||
);
|
||||
const subscriptionItems = items.filter(
|
||||
item => item.billingInterval === 'monthly' || item.billingType === 'subscription'
|
||||
);
|
||||
return { purchaseItems, subscriptionItems };
|
||||
}
|
||||
110
shop/lib/wizard-state.test.ts
Normal file
110
shop/lib/wizard-state.test.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { handleBaseProductChange, handleModuleToggle } from './wizard-state';
|
||||
import { Product, ProductModule, Category, WizardSelections } from './types';
|
||||
|
||||
describe('Wizard State Management', () => {
|
||||
const mockCategories: Category[] = [
|
||||
{
|
||||
id: 'cat-1',
|
||||
name: 'Kasse',
|
||||
sort_order: 1,
|
||||
is_required: true,
|
||||
allow_multiselect: false,
|
||||
free_items_limit: 0,
|
||||
preselect: false,
|
||||
resets_others: false,
|
||||
created_at: '',
|
||||
},
|
||||
{
|
||||
id: 'cat-2',
|
||||
name: 'Backoffice',
|
||||
sort_order: 2,
|
||||
is_required: false,
|
||||
allow_multiselect: false, // Radio behavior
|
||||
free_items_limit: 0,
|
||||
preselect: false,
|
||||
resets_others: false,
|
||||
created_at: '',
|
||||
}
|
||||
];
|
||||
|
||||
const mockModules: ProductModule[] = [
|
||||
{
|
||||
id: 'mod-cloud',
|
||||
product_id: 'prod-gastro',
|
||||
name: 'POSCloud',
|
||||
price: 10,
|
||||
requirements: [],
|
||||
exclusions: [],
|
||||
created_at: '',
|
||||
},
|
||||
{
|
||||
id: 'mod-office-1',
|
||||
product_id: 'prod-gastro',
|
||||
name: 'Office Pro',
|
||||
price: 20,
|
||||
requirements: [],
|
||||
exclusions: [],
|
||||
created_at: '',
|
||||
},
|
||||
{
|
||||
id: 'mod-office-2',
|
||||
product_id: 'prod-gastro',
|
||||
name: 'Office Light',
|
||||
price: 5,
|
||||
requirements: [],
|
||||
exclusions: [],
|
||||
created_at: '',
|
||||
}
|
||||
];
|
||||
|
||||
it('should automatically remove excluded modules when switching base product', () => {
|
||||
const currentSelections: WizardSelections = {
|
||||
'cat-2': {
|
||||
productId: null,
|
||||
moduleIds: ['mod-cloud'] // User has POSCloud selected
|
||||
}
|
||||
};
|
||||
|
||||
// Exclusion rule: 'prod-small' excludes 'mod-cloud'
|
||||
const mockExclusions = [
|
||||
{
|
||||
product_id: 'prod-small',
|
||||
excluded_module_id: 'mod-cloud'
|
||||
}
|
||||
];
|
||||
|
||||
const result = handleBaseProductChange(
|
||||
'prod-small',
|
||||
currentSelections,
|
||||
mockModules,
|
||||
[],
|
||||
mockExclusions
|
||||
);
|
||||
|
||||
// POSCloud should be removed
|
||||
expect(result['cat-2'].moduleIds).not.toContain('mod-cloud');
|
||||
});
|
||||
|
||||
it('should respect allow_multiselect = false and only allow one selection in that category', () => {
|
||||
let selections: WizardSelections = {
|
||||
'cat-2': {
|
||||
productId: null,
|
||||
moduleIds: ['mod-office-1']
|
||||
}
|
||||
};
|
||||
|
||||
// Toggle on 'mod-office-2' in a single-select category
|
||||
selections = handleModuleToggle(
|
||||
'mod-office-2',
|
||||
'cat-2',
|
||||
selections,
|
||||
mockCategories,
|
||||
mockModules
|
||||
);
|
||||
|
||||
// Should only contain 'mod-office-2'
|
||||
expect(selections['cat-2'].moduleIds).toContain('mod-office-2');
|
||||
expect(selections['cat-2'].moduleIds).not.toContain('mod-office-1');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user