Skip to content

Commit 294f6ce

Browse files
authored
W 17819854/fix link hreflang (#2269)
* Fix hreflang links
1 parent 0173f8d commit 294f6ce

File tree

4 files changed

+89
-33
lines changed

4 files changed

+89
-33
lines changed

packages/template-retail-react-app/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
## v6.0.0
22
- DNT Consent Banner: [#2203](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/2203)
33
- Implemented opt-in Social & Passwordless Login features and fixed the Reset Password flow which now leverages SLAS APIs [#2079] (https://github.com/SalesforceCommerceCloud/pwa-kit/pull/2079)
4+
- Fix hreflang alternate links [#2269](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/2269)
45

56
## v5.1.0-dev (TBD)
67

packages/template-retail-react-app/app/components/_app/index.jsx

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ import {
8080

8181
import Seo from '@salesforce/retail-react-app/app/components/seo'
8282
import {Helmet} from 'react-helmet'
83+
import {getPathWithLocale} from '@salesforce/retail-react-app/app/utils/url'
8384

8485
const PlaceholderComponent = () => (
8586
<Center p="2">
@@ -336,15 +337,25 @@ const App = (props) => {
336337
<link
337338
rel="alternate"
338339
hrefLang={locale.id.toLowerCase()}
339-
href={`${appOrigin}${buildUrl(location.pathname)}`}
340+
href={`${appOrigin}${getPathWithLocale(locale.id, buildUrl, {
341+
location: {
342+
...location,
343+
search: ''
344+
}
345+
})}`}
340346
key={locale.id}
341347
/>
342348
))}
343349
{/* A general locale as fallback. For example: "en" if default locale is "en-GB" */}
344350
<link
345351
rel="alternate"
346352
hrefLang={site.l10n.defaultLocale.slice(0, 2)}
347-
href={`${appOrigin}${buildUrl(location.pathname)}`}
353+
href={`${appOrigin}${getPathWithLocale(locale.id, buildUrl, {
354+
location: {
355+
...location,
356+
search: ''
357+
}
358+
})}`}
348359
/>
349360
{/* A wider fallback for user locales that the app does not support */}
350361
<link rel="alternate" hrefLang="x-default" href={`${appOrigin}/`} />

packages/template-retail-react-app/app/components/_app/index.test.js

Lines changed: 73 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -16,37 +16,18 @@ import {DEFAULT_LOCALE} from '@salesforce/retail-react-app/app/utils/test-utils'
1616
import useMultiSite from '@salesforce/retail-react-app/app/hooks/use-multi-site'
1717
import messages from '@salesforce/retail-react-app/app/static/translations/compiled/en-GB.json'
1818
import mockConfig from '@salesforce/retail-react-app/config/mocks/default'
19-
import * as constants from '@salesforce/retail-react-app/app/constants'
19+
import {prependHandlersToServer} from '@salesforce/retail-react-app/jest-setup'
20+
import {mockCustomerBaskets} from '@salesforce/retail-react-app/app/mocks/mock-data'
2021

2122
jest.mock('../../hooks/use-multi-site', () => jest.fn())
2223
jest.mock('../../hooks/use-update-shopper-context', () => ({
2324
useUpdateShopperContext: jest.fn()
2425
}))
2526

2627
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-
})
4828

4929
const mockUpdateDNT = jest.fn()
30+
const mockActiveDataFlag = jest.fn()
5031
jest.mock('@salesforce/commerce-sdk-react', () => {
5132
const originalModule = jest.requireActual('@salesforce/commerce-sdk-react')
5233
return {
@@ -55,6 +36,40 @@ jest.mock('@salesforce/commerce-sdk-react', () => {
5536
}
5637
})
5738

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+
})
5873
describe('App', () => {
5974
const site = {
6075
...mockConfig.app.sites[0],
@@ -89,7 +104,7 @@ describe('App', () => {
89104
})
90105

91106
test('Active Data component is not rendered', async () => {
92-
constants.ACTIVE_DATA_ENABLED = false
107+
mockActiveDataFlag.mockImplementation(() => false)
93108
useMultiSite.mockImplementation(() => resultUseMultiSite)
94109
renderWithProviders(
95110
<App targetLocale={DEFAULT_LOCALE} defaultLocale={DEFAULT_LOCALE} messages={messages}>
@@ -105,17 +120,18 @@ describe('App', () => {
105120
})
106121

107122
test('Active Data component is rendered appropriately', async () => {
108-
constants.ACTIVE_DATA_ENABLED = true
109123
useMultiSite.mockImplementation(() => resultUseMultiSite)
110124
renderWithProviders(
111125
<App targetLocale={DEFAULT_LOCALE} defaultLocale={DEFAULT_LOCALE} messages={messages}>
112126
<p>Any children here</p>
113127
</App>
114128
)
115-
await waitFor(() => expect(document.getElementById('headActiveData')).toBeInTheDocument())
116-
await waitFor(() => expect(document.getElementById('dwanalytics')).toBeInTheDocument())
117-
await waitFor(() => expect(document.getElementById('dwac')).toBeInTheDocument())
118-
expect(screen.getByText('Any children here')).toBeInTheDocument()
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+
})
119135
})
120136

121137
test('The localized hreflang links exist in the html head', () => {
@@ -124,11 +140,39 @@ describe('App', () => {
124140
<App targetLocale={DEFAULT_LOCALE} defaultLocale={DEFAULT_LOCALE} messages={messages} />
125141
)
126142

143+
// expected locales for hrefLang
144+
const hrefLangLocales = mockConfig.app.sites[0].l10n.supportedLocales.map(
145+
(locale) => locale.id
146+
)
127147
const helmet = Helmet.peek()
128148
const hreflangLinks = helmet.linkTags.filter((link) => link.rel === 'alternate')
129-
130149
const hasGeneralLocale = ({hrefLang}) => hrefLang === DEFAULT_LOCALE.slice(0, 2)
131150

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+
132176
// `length + 2` because one for a general locale and the other with x-default value
133177
expect(hreflangLinks).toHaveLength(resultUseMultiSite.site.l10n.supportedLocales.length + 2)
134178

packages/template-retail-react-app/app/components/breadcrumb/index.jsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ const Breadcrumb = ({categories, ...rest}) => {
3636
return (
3737
<ChakraBreadcrumb
3838
className="sf-breadcrumb"
39-
{...styles.container}
39+
sx={styles.container}
4040
separator={<ChevronRightIcon {...styles.icon} aria-hidden="true" />}
4141
{...rest}
4242
>
@@ -45,7 +45,7 @@ const Breadcrumb = ({categories, ...rest}) => {
4545
<ChakraBreadcrumbLink
4646
as={RouteLink}
4747
to={categoryUrlBuilder(category, intl.locale)}
48-
{...styles.link}
48+
sx={styles.link}
4949
>
5050
{category.name}
5151
</ChakraBreadcrumbLink>

0 commit comments

Comments
 (0)