@salesforce/retail-react-app 6.0.1 → 6.1.0-dev
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/CHANGELOG.md +7 -0
- package/app/components/_app/index.jsx +13 -2
- package/app/components/_app/index.test.js +73 -29
- package/app/components/breadcrumb/index.jsx +2 -2
- package/app/components/links-list/index.test.js +0 -2
- package/app/components/product-view-modal/bundle.test.js +6 -1
- package/app/components/recommended-products/index.jsx +9 -0
- package/app/components/store-locator-modal/store-locator-content.test.jsx +0 -1
- package/app/hooks/use-datacloud.js +481 -0
- package/app/hooks/use-datacloud.test.js +164 -0
- package/app/mocks/datacloud-mock-data.js +404 -0
- package/app/pages/account/index.jsx +3 -0
- package/app/pages/account/index.test.js +36 -36
- package/app/pages/home/index.jsx +3 -0
- package/app/pages/login/index.jsx +3 -0
- package/app/pages/product-detail/index.jsx +16 -2
- package/app/pages/product-list/index.jsx +15 -1
- package/app/pages/registration/index.jsx +3 -0
- package/app/pages/reset-password/index.jsx +3 -0
- package/app/ssr.js +3 -1
- package/config/default.js +4 -0
- package/config/mocks/default.js +4 -0
- package/jest-setup.js +10 -0
- package/jest.config.js +5 -1
- package/package.json +15 -9
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
## v6.1.0-dev (Feb 18, 2025)
|
|
2
|
+
|
|
3
|
+
- Fix hreflang alternate links [#2269](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/2269)
|
|
4
|
+
- PDP / PLP: Add page meta data tags that have been defined in BM [#2232](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/2232)
|
|
5
|
+
- Send PWA Kit events to Data Cloud [#318] (https://github.com/SalesforceCommerceCloud/pwa-kit/pull/2229)
|
|
6
|
+
- Fix dependencies vulnerabilities [#2338](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/2338)
|
|
7
|
+
|
|
1
8
|
## v6.0.1 (Mar 05, 2025)
|
|
2
9
|
- Update PWA-Kit SDKs to v3.9.1 [#2301](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/2301)
|
|
3
10
|
|
|
@@ -80,6 +80,7 @@ import {
|
|
|
80
80
|
|
|
81
81
|
import Seo from '@salesforce/retail-react-app/app/components/seo'
|
|
82
82
|
import {Helmet} from 'react-helmet'
|
|
83
|
+
import {getPathWithLocale} from '@salesforce/retail-react-app/app/utils/url'
|
|
83
84
|
|
|
84
85
|
const PlaceholderComponent = () => (
|
|
85
86
|
<Center p="2">
|
|
@@ -336,7 +337,12 @@ const App = (props) => {
|
|
|
336
337
|
<link
|
|
337
338
|
rel="alternate"
|
|
338
339
|
hrefLang={locale.id.toLowerCase()}
|
|
339
|
-
href={`${appOrigin}${
|
|
340
|
+
href={`${appOrigin}${getPathWithLocale(locale.id, buildUrl, {
|
|
341
|
+
location: {
|
|
342
|
+
...location,
|
|
343
|
+
search: ''
|
|
344
|
+
}
|
|
345
|
+
})}`}
|
|
340
346
|
key={locale.id}
|
|
341
347
|
/>
|
|
342
348
|
))}
|
|
@@ -344,7 +350,12 @@ const App = (props) => {
|
|
|
344
350
|
<link
|
|
345
351
|
rel="alternate"
|
|
346
352
|
hrefLang={site.l10n.defaultLocale.slice(0, 2)}
|
|
347
|
-
href={`${appOrigin}${
|
|
353
|
+
href={`${appOrigin}${getPathWithLocale(locale.id, buildUrl, {
|
|
354
|
+
location: {
|
|
355
|
+
...location,
|
|
356
|
+
search: ''
|
|
357
|
+
}
|
|
358
|
+
})}`}
|
|
348
359
|
/>
|
|
349
360
|
{/* A wider fallback for user locales that the app does not support */}
|
|
350
361
|
<link rel="alternate" hrefLang="x-default" href={`${appOrigin}/`} />
|
|
@@ -16,7 +16,8 @@ import {DEFAULT_LOCALE} from '@salesforce/retail-react-app/app/utils/test-utils'
|
|
|
16
16
|
import useMultiSite from '@salesforce/retail-react-app/app/hooks/use-multi-site'
|
|
17
17
|
import messages from '@salesforce/retail-react-app/app/static/translations/compiled/en-GB.json'
|
|
18
18
|
import mockConfig from '@salesforce/retail-react-app/config/mocks/default'
|
|
19
|
-
import
|
|
19
|
+
import {prependHandlersToServer} from '@salesforce/retail-react-app/jest-setup'
|
|
20
|
+
import {mockCustomerBaskets} from '@salesforce/retail-react-app/app/mocks/mock-data'
|
|
20
21
|
|
|
21
22
|
jest.mock('../../hooks/use-multi-site', () => jest.fn())
|
|
22
23
|
jest.mock('../../hooks/use-update-shopper-context', () => ({
|
|
@@ -24,29 +25,9 @@ jest.mock('../../hooks/use-update-shopper-context', () => ({
|
|
|
24
25
|
}))
|
|
25
26
|
|
|
26
27
|
let windowSpy
|
|
27
|
-
let originalValue
|
|
28
|
-
beforeAll(() => {
|
|
29
|
-
jest.spyOn(console, 'log').mockImplementation(jest.fn())
|
|
30
|
-
jest.spyOn(console, 'groupCollapsed').mockImplementation(jest.fn())
|
|
31
|
-
originalValue = constants.ACTIVE_DATA_ENABLED
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
afterAll(() => {
|
|
35
|
-
console.log.mockRestore()
|
|
36
|
-
console.groupCollapsed.mockRestore()
|
|
37
|
-
constants.ACTIVE_DATA_ENABLED = originalValue
|
|
38
|
-
})
|
|
39
|
-
beforeEach(() => {
|
|
40
|
-
windowSpy = jest.spyOn(window, 'window', 'get')
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
afterEach(() => {
|
|
44
|
-
console.log.mockClear()
|
|
45
|
-
console.groupCollapsed.mockClear()
|
|
46
|
-
windowSpy.mockRestore()
|
|
47
|
-
})
|
|
48
28
|
|
|
49
29
|
const mockUpdateDNT = jest.fn()
|
|
30
|
+
const mockActiveDataFlag = jest.fn()
|
|
50
31
|
jest.mock('@salesforce/commerce-sdk-react', () => {
|
|
51
32
|
const originalModule = jest.requireActual('@salesforce/commerce-sdk-react')
|
|
52
33
|
return {
|
|
@@ -55,6 +36,40 @@ jest.mock('@salesforce/commerce-sdk-react', () => {
|
|
|
55
36
|
}
|
|
56
37
|
})
|
|
57
38
|
|
|
39
|
+
jest.mock('@salesforce/retail-react-app/app/constants', () => {
|
|
40
|
+
const originalModule = jest.requireActual('@salesforce/retail-react-app/app/constants')
|
|
41
|
+
return {
|
|
42
|
+
...originalModule,
|
|
43
|
+
get ACTIVE_DATA_ENABLED() {
|
|
44
|
+
return mockActiveDataFlag()
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
})
|
|
48
|
+
beforeEach(() => {
|
|
49
|
+
windowSpy = jest.spyOn(window, 'window', 'get')
|
|
50
|
+
mockActiveDataFlag.mockReturnValue(true)
|
|
51
|
+
prependHandlersToServer([
|
|
52
|
+
{
|
|
53
|
+
path: '*/baskets/:basketId/customer',
|
|
54
|
+
method: 'put',
|
|
55
|
+
res: () => {
|
|
56
|
+
return {
|
|
57
|
+
...mockCustomerBaskets.baskets[0],
|
|
58
|
+
customerInfo: {
|
|
59
|
+
customerId: 'abmuc2wupJxeoRxuo3wqYYmbhI',
|
|
60
|
+
email: 'shopperUpdate@salesforce-test.com'
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
])
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
afterEach(() => {
|
|
69
|
+
windowSpy.mockRestore()
|
|
70
|
+
jest.restoreAllMocks()
|
|
71
|
+
jest.resetModules()
|
|
72
|
+
})
|
|
58
73
|
describe('App', () => {
|
|
59
74
|
const site = {
|
|
60
75
|
...mockConfig.app.sites[0],
|
|
@@ -89,7 +104,7 @@ describe('App', () => {
|
|
|
89
104
|
})
|
|
90
105
|
|
|
91
106
|
test('Active Data component is not rendered', async () => {
|
|
92
|
-
|
|
107
|
+
mockActiveDataFlag.mockImplementation(() => false)
|
|
93
108
|
useMultiSite.mockImplementation(() => resultUseMultiSite)
|
|
94
109
|
renderWithProviders(
|
|
95
110
|
<App targetLocale={DEFAULT_LOCALE} defaultLocale={DEFAULT_LOCALE} messages={messages}>
|
|
@@ -105,17 +120,18 @@ describe('App', () => {
|
|
|
105
120
|
})
|
|
106
121
|
|
|
107
122
|
test('Active Data component is rendered appropriately', async () => {
|
|
108
|
-
constants.ACTIVE_DATA_ENABLED = true
|
|
109
123
|
useMultiSite.mockImplementation(() => resultUseMultiSite)
|
|
110
124
|
renderWithProviders(
|
|
111
125
|
<App targetLocale={DEFAULT_LOCALE} defaultLocale={DEFAULT_LOCALE} messages={messages}>
|
|
112
126
|
<p>Any children here</p>
|
|
113
127
|
</App>
|
|
114
128
|
)
|
|
115
|
-
await waitFor(() =>
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
129
|
+
await waitFor(() => {
|
|
130
|
+
expect(document.getElementById('headActiveData')).toBeInTheDocument()
|
|
131
|
+
expect(document.getElementById('dwanalytics')).toBeInTheDocument()
|
|
132
|
+
expect(document.getElementById('dwac')).toBeInTheDocument()
|
|
133
|
+
expect(screen.getByText('Any children here')).toBeInTheDocument()
|
|
134
|
+
})
|
|
119
135
|
})
|
|
120
136
|
|
|
121
137
|
test('The localized hreflang links exist in the html head', () => {
|
|
@@ -124,11 +140,39 @@ describe('App', () => {
|
|
|
124
140
|
<App targetLocale={DEFAULT_LOCALE} defaultLocale={DEFAULT_LOCALE} messages={messages} />
|
|
125
141
|
)
|
|
126
142
|
|
|
143
|
+
// expected locales for hrefLang
|
|
144
|
+
const hrefLangLocales = mockConfig.app.sites[0].l10n.supportedLocales.map(
|
|
145
|
+
(locale) => locale.id
|
|
146
|
+
)
|
|
127
147
|
const helmet = Helmet.peek()
|
|
128
148
|
const hreflangLinks = helmet.linkTags.filter((link) => link.rel === 'alternate')
|
|
129
|
-
|
|
130
149
|
const hasGeneralLocale = ({hrefLang}) => hrefLang === DEFAULT_LOCALE.slice(0, 2)
|
|
131
150
|
|
|
151
|
+
hrefLangLocales.forEach((supportedLocale) => {
|
|
152
|
+
expect(
|
|
153
|
+
hreflangLinks.some(
|
|
154
|
+
(link) => link.hrefLang.toLowerCase() === supportedLocale.toLowerCase()
|
|
155
|
+
)
|
|
156
|
+
).toBe(true)
|
|
157
|
+
expect(hreflangLinks.some((link) => hasGeneralLocale(link))).toBe(true)
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
// localeRefs takes locale alias into consideration
|
|
161
|
+
const localeRefs = mockConfig.app.sites[0].l10n.supportedLocales.map(
|
|
162
|
+
(locale) => locale.alias || locale.id
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
localeRefs.forEach((localeRef) => {
|
|
166
|
+
expect(hreflangLinks.some((link) => link.href.includes(localeRef))).toBe(true)
|
|
167
|
+
// expecting href does not contain search query params in the href since it is a canonical url
|
|
168
|
+
expect(
|
|
169
|
+
hreflangLinks.some((link) => {
|
|
170
|
+
const urlObj = new URL(link.href)
|
|
171
|
+
return urlObj.search.length > 0
|
|
172
|
+
})
|
|
173
|
+
).toBe(false)
|
|
174
|
+
})
|
|
175
|
+
|
|
132
176
|
// `length + 2` because one for a general locale and the other with x-default value
|
|
133
177
|
expect(hreflangLinks).toHaveLength(resultUseMultiSite.site.l10n.supportedLocales.length + 2)
|
|
134
178
|
|
|
@@ -36,7 +36,7 @@ const Breadcrumb = ({categories, ...rest}) => {
|
|
|
36
36
|
return (
|
|
37
37
|
<ChakraBreadcrumb
|
|
38
38
|
className="sf-breadcrumb"
|
|
39
|
-
{
|
|
39
|
+
sx={styles.container}
|
|
40
40
|
separator={<ChevronRightIcon {...styles.icon} aria-hidden="true" />}
|
|
41
41
|
{...rest}
|
|
42
42
|
>
|
|
@@ -45,7 +45,7 @@ const Breadcrumb = ({categories, ...rest}) => {
|
|
|
45
45
|
<ChakraBreadcrumbLink
|
|
46
46
|
as={RouteLink}
|
|
47
47
|
to={categoryUrlBuilder(category, intl.locale)}
|
|
48
|
-
{
|
|
48
|
+
sx={styles.link}
|
|
49
49
|
>
|
|
50
50
|
{category.name}
|
|
51
51
|
</ChakraBreadcrumbLink>
|
|
@@ -60,7 +60,5 @@ test('renders LinksList with horizontal variant', () => {
|
|
|
60
60
|
<LinksList links={links} variant="horizontal" />
|
|
61
61
|
</FooterStylesProvider>
|
|
62
62
|
)
|
|
63
|
-
screen.logTestingPlaygroundURL()
|
|
64
|
-
|
|
65
63
|
expect(container.querySelector(horizontalVariantSelector)).toBeInTheDocument()
|
|
66
64
|
})
|
|
@@ -63,6 +63,11 @@ beforeEach(() => {
|
|
|
63
63
|
)
|
|
64
64
|
})
|
|
65
65
|
|
|
66
|
+
afterEach(() => {
|
|
67
|
+
jest.resetModules()
|
|
68
|
+
jest.restoreAllMocks()
|
|
69
|
+
})
|
|
70
|
+
|
|
66
71
|
test('renders bundle product view modal', async () => {
|
|
67
72
|
renderWithProviders(<MockComponent />)
|
|
68
73
|
await waitFor(() => {
|
|
@@ -165,9 +170,9 @@ test('bundle product view modal disables update button when quantity exceeds chi
|
|
|
165
170
|
})
|
|
166
171
|
|
|
167
172
|
// Set product bundle quantity selection to 4
|
|
168
|
-
quantityInput.focus()
|
|
169
173
|
fireEvent.change(quantityInput, {target: {value: '4'}})
|
|
170
174
|
fireEvent.keyDown(quantityInput, {key: 'Enter', code: 'Enter', charCode: 13})
|
|
175
|
+
|
|
171
176
|
fireEvent.click(sizeSelectBtn)
|
|
172
177
|
|
|
173
178
|
await waitFor(() => {
|
|
@@ -11,6 +11,7 @@ import {useIntl} from 'react-intl'
|
|
|
11
11
|
import {Button} from '@salesforce/retail-react-app/app/components/shared/ui'
|
|
12
12
|
import ProductScroller from '@salesforce/retail-react-app/app/components/product-scroller'
|
|
13
13
|
import useEinstein from '@salesforce/retail-react-app/app/hooks/use-einstein'
|
|
14
|
+
import useDataCloud from '@salesforce/retail-react-app/app/hooks/use-datacloud'
|
|
14
15
|
import {useCurrentCustomer} from '@salesforce/retail-react-app/app/hooks/use-current-customer'
|
|
15
16
|
import useIntersectionObserver from '@salesforce/retail-react-app/app/hooks/use-intersection-observer'
|
|
16
17
|
import {useWishList} from '@salesforce/retail-react-app/app/hooks/use-wish-list'
|
|
@@ -41,6 +42,7 @@ const RecommendedProducts = ({zone, recommender, products, title, shouldFetch, .
|
|
|
41
42
|
const {data: customer} = useCurrentCustomer()
|
|
42
43
|
const {customerId} = customer
|
|
43
44
|
const {data: wishlist} = useWishList()
|
|
45
|
+
const dataCloud = useDataCloud()
|
|
44
46
|
|
|
45
47
|
const createCustomerProductListItem = useShopperCustomersMutation(
|
|
46
48
|
'createCustomerProductListItem'
|
|
@@ -98,6 +100,13 @@ const RecommendedProducts = ({zone, recommender, products, title, shouldFetch, .
|
|
|
98
100
|
},
|
|
99
101
|
recommendations.recs.map((rec) => ({id: rec.id}))
|
|
100
102
|
)
|
|
103
|
+
dataCloud.sendViewRecommendations(
|
|
104
|
+
{
|
|
105
|
+
recommenderName: recommendations.recommenderName,
|
|
106
|
+
__recoUUID: recommendations.recoUUID
|
|
107
|
+
},
|
|
108
|
+
recommendations.recs.map((rec) => ({id: rec.id}))
|
|
109
|
+
)
|
|
101
110
|
}
|
|
102
111
|
}, [isOnScreen, recommendations])
|
|
103
112
|
|