diff --git a/libs/ui/src/domain/usecases/menuItemDetail.test.ts b/libs/ui/src/domain/usecases/menuItemDetail.test.ts index dc6fcbfc..caf3e3a6 100644 --- a/libs/ui/src/domain/usecases/menuItemDetail.test.ts +++ b/libs/ui/src/domain/usecases/menuItemDetail.test.ts @@ -255,6 +255,68 @@ describe('MenuItemDetailUsecase', () => { expect(menuItemDetail.state.amount).toBe(3); }); + it('lowers the amount to the new variant remaining quantity when the variant changes', async () => { + const repository = new MockMenuRepository(); + repository.variants = repository.variants.map((variant) => + variant.id === 1 + ? { ...variant, sellableQuantity: 5 } + : variant.id === 2 + ? { ...variant, sellableQuantity: 3 } + : variant + ); + const menuItemDetail = createTester(repository, { productId: 1 }); + + await flushPromises(); + menuItemDetail.dispatch({ + type: 'SELECT_OPTION_VALUE', + optionId: 1, + optionValueId: 1, + }); + await flushPromises(); + menuItemDetail.dispatch({ type: 'CHANGE_AMOUNT', amount: 5 }); + expect(menuItemDetail.state.amount).toBe(5); + + menuItemDetail.dispatch({ + type: 'SELECT_OPTION_VALUE', + optionId: 1, + optionValueId: 2, + }); + await flushPromises(); + + expect(menuItemDetail.state.variant?.id).toBe(2); + expect(menuItemDetail.state.amount).toBe(3); + }); + + it('keeps the amount when it already fits the new variant remaining quantity', async () => { + const repository = new MockMenuRepository(); + repository.variants = repository.variants.map((variant) => + variant.id === 1 + ? { ...variant, sellableQuantity: 5 } + : variant.id === 2 + ? { ...variant, sellableQuantity: 3 } + : variant + ); + const menuItemDetail = createTester(repository, { productId: 1 }); + + await flushPromises(); + menuItemDetail.dispatch({ + type: 'SELECT_OPTION_VALUE', + optionId: 1, + optionValueId: 1, + }); + await flushPromises(); + menuItemDetail.dispatch({ type: 'CHANGE_AMOUNT', amount: 2 }); + + menuItemDetail.dispatch({ + type: 'SELECT_OPTION_VALUE', + optionId: 1, + optionValueId: 2, + }); + await flushPromises(); + + expect(menuItemDetail.state.amount).toBe(2); + }); + it('does not cap the amount for a sold-out resolved variant', async () => { const repository = new MockMenuRepository(); repository.variants = repository.variants.map((variant) => diff --git a/libs/ui/src/domain/usecases/menuItemDetail.ts b/libs/ui/src/domain/usecases/menuItemDetail.ts index 36aa0769..527bef26 100644 --- a/libs/ui/src/domain/usecases/menuItemDetail.ts +++ b/libs/ui/src/domain/usecases/menuItemDetail.ts @@ -82,6 +82,12 @@ function initialSelectedOptionValueIds( return Array.from(new Set([...autoSelected, ...unambiguouslyMatched])); } +function clampAmountToVariant(amount: number, variant: Variant | null): number { + const max = variant?.isSellable ? variant.sellableQuantity : undefined; + const capped = max !== undefined ? Math.min(amount, max) : amount; + return Math.max(1, capped); +} + export class MenuItemDetailUsecase extends Usecase< MenuItemDetailState, MenuItemDetailAction, @@ -235,6 +241,7 @@ export class MenuItemDetailUsecase extends Usecase< ...state, type: 'ready', variant, + amount: clampAmountToVariant(state.amount, variant), }) ) .with( @@ -257,16 +264,10 @@ export class MenuItemDetailUsecase extends Usecase< }, { type: 'CHANGE_AMOUNT' }, ], - ([state, { amount }]) => { - const max = state.variant?.isSellable - ? state.variant.sellableQuantity - : undefined; - const clamped = max !== undefined ? Math.min(amount, max) : amount; - return { - ...state, - amount: Math.max(1, clamped), - }; - } + ([state, { amount }]) => ({ + ...state, + amount: clampAmountToVariant(amount, state.variant), + }) ) .with( [