@faststore/core 4.7.1-dev.1 → 4.8.0-dev.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-generate.log +3 -3
- package/.turbo/turbo-test.log +104 -104
- package/CHANGELOG.md +21 -0
- package/cms/faststore/components/cms_component__cartsidebar.jsonc +74 -0
- package/cms/faststore/schema.json +78 -0
- package/cms/faststore/sections.json +78 -0
- package/package.json +6 -6
- package/src/components/cart/CartRecommendationShelf/CartRecommendationProductCard.tsx +88 -0
- package/src/components/cart/CartRecommendationShelf/CartRecommendationShelf.tsx +110 -0
- package/src/components/cart/CartRecommendationShelf/index.ts +4 -0
- package/src/components/cart/CartRecommendationShelf/section.module.scss +124 -0
- package/src/components/cart/CartSidebar/CartSidebar.tsx +47 -1
- package/src/components/product/ProductCard/ProductCard.tsx +5 -3
- package/src/components/sections/RecommendationShelf/README.md +74 -6
- package/src/components/sections/RecommendationShelf/RecommendationShelf.tsx +15 -3
- package/src/components/sections/RecommendationShelf/RecommendationShelf.types.ts +8 -2
- package/src/components/sections/RecommendationShelf/index.ts +7 -1
- package/src/sdk/recommendations/useRecommendationShelf.ts +2 -2
- package/test/components/CartRecommendationShelf/CartRecommendationProductCard.browser.test.tsx +125 -0
- package/test/components/CartRecommendationShelf/CartRecommendationShelf.browser.test.tsx +286 -0
- package/test/components/CartSidebar/CartSidebar.browser.test.tsx +209 -0
- package/test/components/RecommendationShelf/RecommendationShelf.browser.test.tsx +19 -0
- package/test/sdk/recommendations/useRecommendationShelf.browser.test.tsx +1 -1
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import '@testing-library/jest-dom/vitest'
|
|
2
|
+
import { render } from '@testing-library/react'
|
|
3
|
+
import React from 'react'
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
5
|
+
|
|
6
|
+
vi.mock('next/dynamic', () => ({
|
|
7
|
+
default: (loader: () => Promise<unknown>) => {
|
|
8
|
+
const source = Function.prototype.toString.call(loader)
|
|
9
|
+
const chunk = /webpackChunkName:\s*"([^"]+)"/.exec(source)?.[1] ?? 'dynamic'
|
|
10
|
+
|
|
11
|
+
return function DynamicStub({
|
|
12
|
+
children,
|
|
13
|
+
...props
|
|
14
|
+
}: React.PropsWithChildren<Record<string, unknown>>) {
|
|
15
|
+
const serializableProps = Object.fromEntries(
|
|
16
|
+
Object.entries(props).filter(
|
|
17
|
+
([, value]) =>
|
|
18
|
+
value == null ||
|
|
19
|
+
typeof value === 'string' ||
|
|
20
|
+
typeof value === 'number' ||
|
|
21
|
+
typeof value === 'boolean'
|
|
22
|
+
)
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
return React.createElement(
|
|
26
|
+
'div',
|
|
27
|
+
{
|
|
28
|
+
'data-testid': chunk,
|
|
29
|
+
'data-props': JSON.stringify(serializableProps),
|
|
30
|
+
},
|
|
31
|
+
children
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
}))
|
|
36
|
+
|
|
37
|
+
const useUI = vi.hoisted(() => vi.fn())
|
|
38
|
+
vi.mock('@faststore/ui', () => ({
|
|
39
|
+
Icon: ({ name }: { name?: string }) =>
|
|
40
|
+
React.createElement('span', { 'data-testid': 'icon', 'data-name': name }),
|
|
41
|
+
useFadeEffect: () => ({ fadeOut: vi.fn() }),
|
|
42
|
+
useUI,
|
|
43
|
+
}))
|
|
44
|
+
|
|
45
|
+
const useCart = vi.hoisted(() => vi.fn())
|
|
46
|
+
vi.mock('src/sdk/cart', () => ({ useCart }))
|
|
47
|
+
|
|
48
|
+
vi.mock('src/sdk/cart/useCheckoutButton', () => ({
|
|
49
|
+
useCheckoutButton: () => ({
|
|
50
|
+
onClick: vi.fn(),
|
|
51
|
+
disabled: false,
|
|
52
|
+
'data-testid': 'checkout-button',
|
|
53
|
+
}),
|
|
54
|
+
}))
|
|
55
|
+
|
|
56
|
+
vi.mock('src/sdk/session', () => ({
|
|
57
|
+
useSession: () => ({ currency: { code: 'USD' } }),
|
|
58
|
+
}))
|
|
59
|
+
|
|
60
|
+
vi.mock('@faststore/sdk', () => ({
|
|
61
|
+
sendAnalyticsEvent: vi.fn(),
|
|
62
|
+
}))
|
|
63
|
+
|
|
64
|
+
import CartSidebar from 'src/components/cart/CartSidebar'
|
|
65
|
+
|
|
66
|
+
const CAMPAIGN_VRN = 'vrn:recommendations:acc:rec-cross-v2:campaign-1'
|
|
67
|
+
|
|
68
|
+
const cartItem = {
|
|
69
|
+
id: 'item-1',
|
|
70
|
+
quantity: 1,
|
|
71
|
+
price: 100,
|
|
72
|
+
listPrice: 120,
|
|
73
|
+
itemOffered: {
|
|
74
|
+
sku: 'sku-1',
|
|
75
|
+
name: 'SKU 1',
|
|
76
|
+
gtin: 'gtin-1',
|
|
77
|
+
brand: { name: 'Brand' },
|
|
78
|
+
isVariantOf: { productGroupID: 'pg-1', name: 'Product 1' },
|
|
79
|
+
},
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const sidebarProps = {
|
|
83
|
+
title: 'Your cart',
|
|
84
|
+
alert: {
|
|
85
|
+
icon: { icon: 'Truck', alt: 'Truck' },
|
|
86
|
+
text: 'Free shipping',
|
|
87
|
+
},
|
|
88
|
+
emptyCart: {
|
|
89
|
+
title: 'Your Cart is empty',
|
|
90
|
+
buttonLabel: 'Start Shopping',
|
|
91
|
+
},
|
|
92
|
+
checkoutButton: {
|
|
93
|
+
label: 'Checkout',
|
|
94
|
+
loadingLabel: 'Loading...',
|
|
95
|
+
icon: { icon: 'ArrowRight', alt: 'Arrow Right' },
|
|
96
|
+
},
|
|
97
|
+
quantitySelector: { useUnitMultiplier: false },
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const enabledRecommendations = {
|
|
101
|
+
shouldDisplayRecommendationShelf: true,
|
|
102
|
+
campaignVrn: CAMPAIGN_VRN,
|
|
103
|
+
title: 'You may also like',
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function filledCart() {
|
|
107
|
+
useCart.mockReturnValue({
|
|
108
|
+
items: [cartItem],
|
|
109
|
+
gifts: [],
|
|
110
|
+
totalItems: 1,
|
|
111
|
+
isValidating: false,
|
|
112
|
+
subTotal: 100,
|
|
113
|
+
total: 100,
|
|
114
|
+
subTotalWithTaxes: 110,
|
|
115
|
+
totalWithTaxes: 110,
|
|
116
|
+
})
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function emptyCart() {
|
|
120
|
+
useCart.mockReturnValue({
|
|
121
|
+
items: [],
|
|
122
|
+
gifts: [],
|
|
123
|
+
totalItems: 0,
|
|
124
|
+
isValidating: false,
|
|
125
|
+
subTotal: 0,
|
|
126
|
+
total: 0,
|
|
127
|
+
subTotalWithTaxes: 0,
|
|
128
|
+
totalWithTaxes: 0,
|
|
129
|
+
})
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
beforeEach(() => {
|
|
133
|
+
useUI.mockReturnValue({ cart: true, closeCart: vi.fn() })
|
|
134
|
+
filledCart()
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
afterEach(() => {
|
|
138
|
+
vi.clearAllMocks()
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
describe('CartSidebar recommendation shelf gate', () => {
|
|
142
|
+
it('does not mount the shelf when the display toggle is off', () => {
|
|
143
|
+
const { queryByTestId } = render(
|
|
144
|
+
<CartSidebar
|
|
145
|
+
{...sidebarProps}
|
|
146
|
+
recommendations={{
|
|
147
|
+
shouldDisplayRecommendationShelf: false,
|
|
148
|
+
campaignVrn: CAMPAIGN_VRN,
|
|
149
|
+
}}
|
|
150
|
+
/>
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
expect(queryByTestId('CartRecommendationShelf')).toBeNull()
|
|
154
|
+
expect(queryByTestId('OrderSummary')).toBeTruthy()
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
it('does not mount the shelf when the toggle is on but campaign VRN is missing', () => {
|
|
158
|
+
const { queryByTestId } = render(
|
|
159
|
+
<CartSidebar
|
|
160
|
+
{...sidebarProps}
|
|
161
|
+
recommendations={{ shouldDisplayRecommendationShelf: true }}
|
|
162
|
+
/>
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
expect(queryByTestId('CartRecommendationShelf')).toBeNull()
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
it('does not mount the shelf on an empty cart, even with the toggle on', () => {
|
|
169
|
+
emptyCart()
|
|
170
|
+
|
|
171
|
+
const { queryByTestId } = render(
|
|
172
|
+
<CartSidebar {...sidebarProps} recommendations={enabledRecommendations} />
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
expect(queryByTestId('EmptyCart')).toBeTruthy()
|
|
176
|
+
expect(queryByTestId('CartRecommendationShelf')).toBeNull()
|
|
177
|
+
expect(queryByTestId('OrderSummary')).toBeNull()
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
it('renders the shelf before OrderSummary when the cart has items', () => {
|
|
181
|
+
const { getByTestId } = render(
|
|
182
|
+
<CartSidebar {...sidebarProps} recommendations={enabledRecommendations} />
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
const footerChildren = Array.from(
|
|
186
|
+
getByTestId('UICartSidebarFooter').children
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
expect(footerChildren[0]).toHaveAttribute(
|
|
190
|
+
'data-testid',
|
|
191
|
+
'CartRecommendationShelf'
|
|
192
|
+
)
|
|
193
|
+
expect(footerChildren[1]).toHaveAttribute('data-testid', 'OrderSummary')
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
it('omits shouldDisplayRecommendationShelf from the shelf props', () => {
|
|
197
|
+
const { getByTestId } = render(
|
|
198
|
+
<CartSidebar {...sidebarProps} recommendations={enabledRecommendations} />
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
const props = JSON.parse(
|
|
202
|
+
getByTestId('CartRecommendationShelf').getAttribute('data-props') ?? '{}'
|
|
203
|
+
) as Record<string, unknown>
|
|
204
|
+
|
|
205
|
+
expect(props).not.toHaveProperty('shouldDisplayRecommendationShelf')
|
|
206
|
+
expect(props.campaignVrn).toBe(CAMPAIGN_VRN)
|
|
207
|
+
expect(props.title).toBe('You may also like')
|
|
208
|
+
})
|
|
209
|
+
})
|
|
@@ -75,6 +75,25 @@ afterEach(() => {
|
|
|
75
75
|
})
|
|
76
76
|
|
|
77
77
|
describe('RecommendationShelf', () => {
|
|
78
|
+
it('forwards extra props onto the root section', async () => {
|
|
79
|
+
useRecommendations.mockReturnValue({
|
|
80
|
+
data: recommendationData,
|
|
81
|
+
isLoading: false,
|
|
82
|
+
error: null,
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
const { getByTestId } = render(
|
|
86
|
+
<RecommendationShelf
|
|
87
|
+
campaignVrn={CAMPAIGN_VRN}
|
|
88
|
+
data-testid="recommendation-shelf"
|
|
89
|
+
/>
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
await waitFor(() => {
|
|
93
|
+
expect(getByTestId('recommendation-shelf').tagName).toBe('SECTION')
|
|
94
|
+
})
|
|
95
|
+
})
|
|
96
|
+
|
|
78
97
|
it('renders a product card per recommended product', async () => {
|
|
79
98
|
useRecommendations.mockReturnValue({
|
|
80
99
|
data: recommendationData,
|
|
@@ -151,7 +151,7 @@ describe('useRecommendationShelf', () => {
|
|
|
151
151
|
})
|
|
152
152
|
})
|
|
153
153
|
|
|
154
|
-
it('follows the live cart context
|
|
154
|
+
it('follows the live cart context', () => {
|
|
155
155
|
useCart.mockReturnValue({ items: [cartItem('pg-1')] })
|
|
156
156
|
|
|
157
157
|
const { rerender } = renderHook(() =>
|