import {Product} from '@shopify/shop-minis-platform' import {describe, expect, it, vi} from 'vitest' import { render, screen, mockMinisSDK, resetAllMocks, userEvent, } from '../../test-utils' import {AddToCartButton} from './add-to-cart' // Mock hooks vi.mock('../../internal/useShopCartActions', () => ({ useShopCartActions: () => ({ addToCart: mockMinisSDK.addToCart, buyProduct: mockMinisSDK.buyProduct, }), })) vi.mock('../../hooks', () => ({ useShopNavigation: () => ({ navigateToProduct: mockMinisSDK.navigateToProduct, }), useErrorToast: () => ({ showErrorToast: vi.fn(), }), })) describe('AddToCartButton', () => { const mockProduct: Product = { id: 'gid://shopify/Product/123', title: 'Test Product', reviewAnalytics: { averageRating: null, reviewCount: null, }, shop: { id: 'gid://shopify/Shop/1', name: 'Test Shop', }, defaultVariantId: 'gid://shopify/ProductVariant/456', isFavorited: false, price: { amount: '10.00', currencyCode: 'USD', }, variants: [ { id: 'gid://shopify/ProductVariant/456', title: 'Default', isFavorited: false, availableForSale: true, price: { amount: '10.00', currencyCode: 'USD', }, image: { url: 'https://example.com/variant-image.jpg', altText: null, width: null, height: null, }, }, ], } const defaultProps = { product: mockProduct, productVariantId: 'gid://shopify/ProductVariant/456', } // eslint-disable-next-line jest/require-top-level-describe beforeEach(() => { resetAllMocks() }) it('renders with default text', () => { render() expect(screen.getByRole('button')).toBeInTheDocument() expect(screen.getByText('Add to cart')).toBeInTheDocument() }) it('renders with required props', () => { render() const button = screen.getByRole('button') expect(button).toBeInTheDocument() }) it('respects disabled prop', () => { render() const button = screen.getByRole('button') expect(button).toBeDisabled() }) it('applies custom className', () => { render() const button = screen.getByRole('button') expect(button).toHaveClass('custom-class') }) it('renders with different sizes', () => { const {rerender} = render() expect(screen.getByRole('button')).toBeInTheDocument() rerender() expect(screen.getByRole('button')).toBeInTheDocument() rerender() expect(screen.getByRole('button')).toBeInTheDocument() }) it('renders with discount codes prop', () => { const discountCodes = ['SUMMER20', 'FREESHIP'] render() expect(screen.getByRole('button')).toBeInTheDocument() }) it('calls addToCart when clicked and not a referral product', async () => { const user = userEvent.setup() mockMinisSDK.addToCart.mockResolvedValueOnce({ok: true}) render() const button = screen.getByRole('button') await user.click(button) expect(mockMinisSDK.addToCart).toHaveBeenCalledWith({ productId: mockProduct.id, productVariantId: defaultProps.productVariantId, quantity: 1, discountCodes: undefined, variantImageUrl: 'https://example.com/variant-image.jpg', }) }) it('navigates to product page when product is referral', async () => { const user = userEvent.setup() const referralProduct: Product = {...mockProduct, referral: true} render() const button = screen.getByRole('button') await user.click(button) expect(mockMinisSDK.navigateToProduct).toHaveBeenCalledWith({ productId: referralProduct.id, }) expect(mockMinisSDK.addToCart).not.toHaveBeenCalled() }) it('shows success animation after adding to cart', async () => { const user = userEvent.setup() mockMinisSDK.addToCart.mockResolvedValueOnce({ok: true}) render() const button = screen.getByRole('button') await user.click(button) // Check for success state (Added to cart text) await screen.findByText('Added to cart') }) it('handles add to cart error gracefully', async () => { const user = userEvent.setup() mockMinisSDK.addToCart.mockRejectedValueOnce(new Error('Failed')) render() const button = screen.getByRole('button') await user.click(button) expect(mockMinisSDK.addToCart).toHaveBeenCalled() }) it('does not call addToCart when disabled', async () => { const user = userEvent.setup() render() const button = screen.getByRole('button') await user.click(button) expect(mockMinisSDK.addToCart).not.toHaveBeenCalled() }) it('handles product without variants array', async () => { const user = userEvent.setup() mockMinisSDK.addToCart.mockResolvedValueOnce({ok: true}) const productWithoutVariants: Product = { ...mockProduct, variants: undefined, } render( ) const button = screen.getByRole('button') await user.click(button) expect(mockMinisSDK.addToCart).toHaveBeenCalledWith({ productId: productWithoutVariants.id, productVariantId: 'gid://shopify/ProductVariant/456', quantity: 1, discountCodes: undefined, variantImageUrl: undefined, }) }) it('handles product without matching variant in array', async () => { const user = userEvent.setup() mockMinisSDK.addToCart.mockResolvedValueOnce({ok: true}) const productWithDifferentVariant: Product = { ...mockProduct, variants: [ { id: 'gid://shopify/ProductVariant/999', title: 'Different', isFavorited: false, availableForSale: true, price: { amount: '15.00', currencyCode: 'USD', }, }, ], } render( ) const button = screen.getByRole('button') await user.click(button) expect(mockMinisSDK.addToCart).toHaveBeenCalledWith({ productId: productWithDifferentVariant.id, productVariantId: 'gid://shopify/ProductVariant/456', quantity: 1, discountCodes: undefined, variantImageUrl: undefined, }) }) it('handles product without shop data', async () => { const user = userEvent.setup() mockMinisSDK.addToCart.mockResolvedValueOnce({ok: true}) const productWithoutShop: Product = { ...mockProduct, shop: undefined as any, } render( ) const button = screen.getByRole('button') await user.click(button) expect(mockMinisSDK.addToCart).toHaveBeenCalledWith({ productId: productWithoutShop.id, productVariantId: 'gid://shopify/ProductVariant/456', quantity: 1, discountCodes: undefined, variantImageUrl: 'https://example.com/variant-image.jpg', }) }) describe('sold-out state', () => { const soldOutProduct: Product = { ...mockProduct, variants: [ { id: 'gid://shopify/ProductVariant/456', title: 'Default', isFavorited: false, availableForSale: false, price: { amount: '10.00', currencyCode: 'USD', }, }, ], } it('shows "Sold out" text when matching variant is not available for sale', () => { render( ) expect(screen.getByText('Sold out')).toBeInTheDocument() expect(screen.queryByText('Add to cart')).not.toBeInTheDocument() }) it('disables the button when matching variant is sold out', () => { render( ) expect(screen.getByRole('button')).toBeDisabled() }) it('does not call addToCart when matching variant is sold out', async () => { const user = userEvent.setup() render( ) const button = screen.getByRole('button') await user.click(button) expect(mockMinisSDK.addToCart).not.toHaveBeenCalled() }) it('referral products show "View product" and stay clickable even when sold out', async () => { const user = userEvent.setup() const referralSoldOutProduct: Product = { ...soldOutProduct, referral: true, } render( ) expect(screen.getByText('View product')).toBeInTheDocument() expect(screen.queryByText('Sold out')).not.toBeInTheDocument() const button = screen.getByRole('button') expect(button).toBeEnabled() await user.click(button) expect(mockMinisSDK.navigateToProduct).toHaveBeenCalledWith({ productId: referralSoldOutProduct.id, }) expect(mockMinisSDK.addToCart).not.toHaveBeenCalled() }) it('falls back to selectedVariant when variants array is missing', () => { const productWithSelectedVariant: Product = { ...mockProduct, variants: undefined, selectedVariant: { id: 'gid://shopify/ProductVariant/456', title: 'Default', isFavorited: false, availableForSale: false, price: {amount: '10.00', currencyCode: 'USD'}, }, } render( ) expect(screen.getByText('Sold out')).toBeInTheDocument() expect(screen.getByRole('button')).toBeDisabled() }) }) })