@@ -1639,4 +1639,357 @@ describe('Bonus Product Cart Utilities', () => {
16391639 expect ( result [ 0 ] . productId ) . toBe ( 'bonus-product-1' )
16401640 } )
16411641 } )
1642+
1643+ describe ( 'consolidateDuplicateBonusProducts' , ( ) => {
1644+ test ( 'returns empty array for null input' , ( ) => {
1645+ const result = cartUtils . consolidateDuplicateBonusProducts ( null )
1646+ expect ( result ) . toEqual ( [ ] )
1647+ } )
1648+
1649+ test ( 'returns empty array for undefined input' , ( ) => {
1650+ const result = cartUtils . consolidateDuplicateBonusProducts ( undefined )
1651+ expect ( result ) . toEqual ( [ ] )
1652+ } )
1653+
1654+ test ( 'returns empty array for empty array input' , ( ) => {
1655+ const result = cartUtils . consolidateDuplicateBonusProducts ( [ ] )
1656+ expect ( result ) . toEqual ( [ ] )
1657+ } )
1658+
1659+ test ( 'returns regular products as-is when no bonus products' , ( ) => {
1660+ const productItems = [
1661+ {
1662+ productId : 'regular-product-1' ,
1663+ itemId : 'item-1' ,
1664+ quantity : 2
1665+ } ,
1666+ {
1667+ productId : 'regular-product-2' ,
1668+ itemId : 'item-2' ,
1669+ quantity : 1
1670+ }
1671+ ]
1672+
1673+ const result = cartUtils . consolidateDuplicateBonusProducts ( productItems )
1674+
1675+ expect ( result ) . toHaveLength ( 2 )
1676+ expect ( result [ 0 ] . productId ) . toBe ( 'regular-product-1' )
1677+ expect ( result [ 0 ] . quantity ) . toBe ( 2 )
1678+ expect ( result [ 1 ] . productId ) . toBe ( 'regular-product-2' )
1679+ expect ( result [ 1 ] . quantity ) . toBe ( 1 )
1680+ } )
1681+
1682+ test ( 'consolidates duplicate bonus products by productId' , ( ) => {
1683+ const productItems = [
1684+ {
1685+ productId : 'bonus-product-1' ,
1686+ itemId : 'bonus-item-1' ,
1687+ quantity : 1 ,
1688+ bonusProductLineItem : true ,
1689+ bonusDiscountLineItemId : 'bonus-123'
1690+ } ,
1691+ {
1692+ productId : 'bonus-product-1' ,
1693+ itemId : 'bonus-item-2' ,
1694+ quantity : 2 ,
1695+ bonusProductLineItem : true ,
1696+ bonusDiscountLineItemId : 'bonus-123'
1697+ } ,
1698+ {
1699+ productId : 'bonus-product-1' ,
1700+ itemId : 'bonus-item-3' ,
1701+ quantity : 1 ,
1702+ bonusProductLineItem : true ,
1703+ bonusDiscountLineItemId : 'bonus-456'
1704+ }
1705+ ]
1706+
1707+ const result = cartUtils . consolidateDuplicateBonusProducts ( productItems )
1708+
1709+ expect ( result ) . toHaveLength ( 1 )
1710+ expect ( result [ 0 ] . productId ) . toBe ( 'bonus-product-1' )
1711+ expect ( result [ 0 ] . quantity ) . toBe ( 4 ) // 1 + 2 + 1
1712+ expect ( result [ 0 ] . bonusProductLineItem ) . toBe ( true )
1713+ } )
1714+
1715+ test ( 'keeps different bonus products separate' , ( ) => {
1716+ const productItems = [
1717+ {
1718+ productId : 'bonus-product-1' ,
1719+ itemId : 'bonus-item-1' ,
1720+ quantity : 2 ,
1721+ bonusProductLineItem : true ,
1722+ bonusDiscountLineItemId : 'bonus-123'
1723+ } ,
1724+ {
1725+ productId : 'bonus-product-2' ,
1726+ itemId : 'bonus-item-2' ,
1727+ quantity : 1 ,
1728+ bonusProductLineItem : true ,
1729+ bonusDiscountLineItemId : 'bonus-456'
1730+ }
1731+ ]
1732+
1733+ const result = cartUtils . consolidateDuplicateBonusProducts ( productItems )
1734+
1735+ expect ( result ) . toHaveLength ( 2 )
1736+ expect ( result [ 0 ] . productId ) . toBe ( 'bonus-product-1' )
1737+ expect ( result [ 0 ] . quantity ) . toBe ( 2 )
1738+ expect ( result [ 1 ] . productId ) . toBe ( 'bonus-product-2' )
1739+ expect ( result [ 1 ] . quantity ) . toBe ( 1 )
1740+ } )
1741+
1742+ test ( 'preserves order: regular products first, then bonus products' , ( ) => {
1743+ const productItems = [
1744+ {
1745+ productId : 'bonus-product-1' ,
1746+ itemId : 'bonus-item-1' ,
1747+ quantity : 1 ,
1748+ bonusProductLineItem : true ,
1749+ bonusDiscountLineItemId : 'bonus-123'
1750+ } ,
1751+ {
1752+ productId : 'regular-product-1' ,
1753+ itemId : 'regular-item-1' ,
1754+ quantity : 2
1755+ } ,
1756+ {
1757+ productId : 'bonus-product-2' ,
1758+ itemId : 'bonus-item-2' ,
1759+ quantity : 1 ,
1760+ bonusProductLineItem : true ,
1761+ bonusDiscountLineItemId : 'bonus-456'
1762+ } ,
1763+ {
1764+ productId : 'regular-product-2' ,
1765+ itemId : 'regular-item-2' ,
1766+ quantity : 1
1767+ }
1768+ ]
1769+
1770+ const result = cartUtils . consolidateDuplicateBonusProducts ( productItems )
1771+
1772+ expect ( result ) . toHaveLength ( 4 )
1773+ // Regular products should come first
1774+ expect ( result [ 0 ] . productId ) . toBe ( 'regular-product-1' )
1775+ expect ( result [ 0 ] . bonusProductLineItem ) . toBeUndefined ( )
1776+ expect ( result [ 1 ] . productId ) . toBe ( 'regular-product-2' )
1777+ expect ( result [ 1 ] . bonusProductLineItem ) . toBeUndefined ( )
1778+ // Bonus products should come after
1779+ expect ( result [ 2 ] . productId ) . toBe ( 'bonus-product-1' )
1780+ expect ( result [ 2 ] . bonusProductLineItem ) . toBe ( true )
1781+ expect ( result [ 3 ] . productId ) . toBe ( 'bonus-product-2' )
1782+ expect ( result [ 3 ] . bonusProductLineItem ) . toBe ( true )
1783+ } )
1784+
1785+ test ( 'consolidates multiple duplicate bonus products and preserves other properties' , ( ) => {
1786+ const productItems = [
1787+ {
1788+ productId : 'bonus-product-1' ,
1789+ itemId : 'bonus-item-1' ,
1790+ quantity : 1 ,
1791+ bonusProductLineItem : true ,
1792+ bonusDiscountLineItemId : 'bonus-123'
1793+ } ,
1794+ {
1795+ productId : 'bonus-product-1' ,
1796+ itemId : 'bonus-item-2' ,
1797+ quantity : 3 ,
1798+ bonusProductLineItem : true ,
1799+ bonusDiscountLineItemId : 'bonus-123'
1800+ } ,
1801+ {
1802+ productId : 'bonus-product-2' ,
1803+ itemId : 'bonus-item-3' ,
1804+ quantity : 2 ,
1805+ bonusProductLineItem : true ,
1806+ bonusDiscountLineItemId : 'bonus-456'
1807+ }
1808+ ]
1809+
1810+ const result = cartUtils . consolidateDuplicateBonusProducts ( productItems )
1811+
1812+ expect ( result ) . toHaveLength ( 2 )
1813+ // First bonus product should be consolidated
1814+ expect ( result [ 0 ] . productId ) . toBe ( 'bonus-product-1' )
1815+ expect ( result [ 0 ] . quantity ) . toBe ( 4 ) // 1 + 3
1816+ expect ( result [ 0 ] . bonusProductLineItem ) . toBe ( true )
1817+ expect ( result [ 0 ] . bonusDiscountLineItemId ) . toBe ( 'bonus-123' )
1818+ // Second bonus product should remain separate
1819+ expect ( result [ 1 ] . productId ) . toBe ( 'bonus-product-2' )
1820+ expect ( result [ 1 ] . quantity ) . toBe ( 2 )
1821+ } )
1822+
1823+ test ( 'handles mixed regular and bonus products with duplicates' , ( ) => {
1824+ const productItems = [
1825+ {
1826+ productId : 'regular-product-1' ,
1827+ itemId : 'regular-item-1' ,
1828+ quantity : 1
1829+ } ,
1830+ {
1831+ productId : 'bonus-product-1' ,
1832+ itemId : 'bonus-item-1' ,
1833+ quantity : 1 ,
1834+ bonusProductLineItem : true ,
1835+ bonusDiscountLineItemId : 'bonus-123'
1836+ } ,
1837+ {
1838+ productId : 'regular-product-2' ,
1839+ itemId : 'regular-item-2' ,
1840+ quantity : 2
1841+ } ,
1842+ {
1843+ productId : 'bonus-product-1' ,
1844+ itemId : 'bonus-item-2' ,
1845+ quantity : 2 ,
1846+ bonusProductLineItem : true ,
1847+ bonusDiscountLineItemId : 'bonus-123'
1848+ } ,
1849+ {
1850+ productId : 'bonus-product-2' ,
1851+ itemId : 'bonus-item-3' ,
1852+ quantity : 1 ,
1853+ bonusProductLineItem : true ,
1854+ bonusDiscountLineItemId : 'bonus-456'
1855+ }
1856+ ]
1857+
1858+ const result = cartUtils . consolidateDuplicateBonusProducts ( productItems )
1859+
1860+ expect ( result ) . toHaveLength ( 4 )
1861+ // Regular products first
1862+ expect ( result [ 0 ] . productId ) . toBe ( 'regular-product-1' )
1863+ expect ( result [ 0 ] . quantity ) . toBe ( 1 )
1864+ expect ( result [ 1 ] . productId ) . toBe ( 'regular-product-2' )
1865+ expect ( result [ 1 ] . quantity ) . toBe ( 2 )
1866+ // Consolidated bonus products
1867+ expect ( result [ 2 ] . productId ) . toBe ( 'bonus-product-1' )
1868+ expect ( result [ 2 ] . quantity ) . toBe ( 3 ) // 1 + 2
1869+ expect ( result [ 3 ] . productId ) . toBe ( 'bonus-product-2' )
1870+ expect ( result [ 3 ] . quantity ) . toBe ( 1 )
1871+ } )
1872+
1873+ test ( 'handles bonus products with zero quantity' , ( ) => {
1874+ const productItems = [
1875+ {
1876+ productId : 'bonus-product-1' ,
1877+ itemId : 'bonus-item-1' ,
1878+ quantity : 0 ,
1879+ bonusProductLineItem : true ,
1880+ bonusDiscountLineItemId : 'bonus-123'
1881+ } ,
1882+ {
1883+ productId : 'bonus-product-1' ,
1884+ itemId : 'bonus-item-2' ,
1885+ quantity : 2 ,
1886+ bonusProductLineItem : true ,
1887+ bonusDiscountLineItemId : 'bonus-123'
1888+ }
1889+ ]
1890+
1891+ const result = cartUtils . consolidateDuplicateBonusProducts ( productItems )
1892+
1893+ expect ( result ) . toHaveLength ( 1 )
1894+ expect ( result [ 0 ] . productId ) . toBe ( 'bonus-product-1' )
1895+ expect ( result [ 0 ] . quantity ) . toBe ( 2 ) // 0 + 2
1896+ } )
1897+
1898+ test ( 'handles bonus products with missing quantity property' , ( ) => {
1899+ const productItems = [
1900+ {
1901+ productId : 'bonus-product-1' ,
1902+ itemId : 'bonus-item-1' ,
1903+ bonusProductLineItem : true ,
1904+ bonusDiscountLineItemId : 'bonus-123'
1905+ } ,
1906+ {
1907+ productId : 'bonus-product-1' ,
1908+ itemId : 'bonus-item-2' ,
1909+ quantity : 2 ,
1910+ bonusProductLineItem : true ,
1911+ bonusDiscountLineItemId : 'bonus-123'
1912+ }
1913+ ]
1914+
1915+ const result = cartUtils . consolidateDuplicateBonusProducts ( productItems )
1916+
1917+ expect ( result ) . toHaveLength ( 1 )
1918+ expect ( result [ 0 ] . productId ) . toBe ( 'bonus-product-1' )
1919+ expect ( result [ 0 ] . quantity ) . toBe ( 2 ) // undefined treated as 0, then + 2
1920+ } )
1921+
1922+ test ( 'consolidates same bonus product from different qualifying products' , ( ) => {
1923+ // Scenario: Same product (bonus-product-1) is a bonus for two different qualifying products
1924+ // This tests that consolidation happens by productId only, regardless of which
1925+ // qualifying product triggered the bonus or which bonusDiscountLineItemId it has
1926+ const productItems = [
1927+ {
1928+ productId : 'regular-product-A' ,
1929+ itemId : 'regular-item-A' ,
1930+ quantity : 1
1931+ } ,
1932+ {
1933+ productId : 'bonus-product-1' ,
1934+ itemId : 'bonus-item-1' ,
1935+ quantity : 2 ,
1936+ bonusProductLineItem : true ,
1937+ bonusDiscountLineItemId : 'bonus-123' // From regular-product-A
1938+ } ,
1939+ {
1940+ productId : 'regular-product-B' ,
1941+ itemId : 'regular-item-B' ,
1942+ quantity : 1
1943+ } ,
1944+ {
1945+ productId : 'bonus-product-1' ,
1946+ itemId : 'bonus-item-2' ,
1947+ quantity : 1 ,
1948+ bonusProductLineItem : true ,
1949+ bonusDiscountLineItemId : 'bonus-456' // From regular-product-B (different line item)
1950+ }
1951+ ]
1952+
1953+ const result = cartUtils . consolidateDuplicateBonusProducts ( productItems )
1954+
1955+ expect ( result ) . toHaveLength ( 3 )
1956+ // Regular products first
1957+ expect ( result [ 0 ] . productId ) . toBe ( 'regular-product-A' )
1958+ expect ( result [ 1 ] . productId ) . toBe ( 'regular-product-B' )
1959+ // Bonus products consolidated - same productId from different sources merged
1960+ expect ( result [ 2 ] . productId ) . toBe ( 'bonus-product-1' )
1961+ expect ( result [ 2 ] . quantity ) . toBe ( 3 ) // 2 + 1, consolidated from both qualifying products
1962+ expect ( result [ 2 ] . bonusProductLineItem ) . toBe ( true )
1963+ // Note: The bonusDiscountLineItemId will be from the first item found (bonus-123)
1964+ // This is a side effect of using sampleItem - information about the second source is lost
1965+ } )
1966+
1967+ test ( 'itemId is not used in consolidation logic (only for display keys)' , ( ) => {
1968+ // itemId is unique per line item but consolidation only uses productId
1969+ const productItems = [
1970+ {
1971+ productId : 'bonus-product-1' ,
1972+ itemId : 'unique-item-id-1' ,
1973+ quantity : 1 ,
1974+ bonusProductLineItem : true ,
1975+ bonusDiscountLineItemId : 'bonus-123'
1976+ } ,
1977+ {
1978+ productId : 'bonus-product-1' ,
1979+ itemId : 'unique-item-id-2' , // Different itemId
1980+ quantity : 2 ,
1981+ bonusProductLineItem : true ,
1982+ bonusDiscountLineItemId : 'bonus-123'
1983+ }
1984+ ]
1985+
1986+ const result = cartUtils . consolidateDuplicateBonusProducts ( productItems )
1987+
1988+ expect ( result ) . toHaveLength ( 1 )
1989+ expect ( result [ 0 ] . productId ) . toBe ( 'bonus-product-1' )
1990+ expect ( result [ 0 ] . quantity ) . toBe ( 3 ) // Consolidated despite different itemIds
1991+ // The itemId in result will be from the first item found (unique-item-id-1)
1992+ // This is preserved for React key generation but not used in consolidation logic
1993+ } )
1994+ } )
16421995} )
0 commit comments