diff --git a/shop/lib/checkout-split.test.ts b/shop/lib/checkout-split.test.ts new file mode 100644 index 0000000..5d1f4e4 --- /dev/null +++ b/shop/lib/checkout-split.test.ts @@ -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); + }); +}); diff --git a/shop/lib/checkout-split.ts b/shop/lib/checkout-split.ts new file mode 100644 index 0000000..df6b6b9 --- /dev/null +++ b/shop/lib/checkout-split.ts @@ -0,0 +1,17 @@ +export interface CartItem { + id?: string; + billingInterval?: 'one_time' | 'monthly'; + billingType?: 'purchase' | 'subscription'; + selections: any; + moduleQuantities?: Record; +} + +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 }; +} diff --git a/shop/lib/wizard-state.test.ts b/shop/lib/wizard-state.test.ts new file mode 100644 index 0000000..0e2a6fe --- /dev/null +++ b/shop/lib/wizard-state.test.ts @@ -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'); + }); +});