Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move some util to site-utils to avoid circular imports #1133

Merged
merged 7 commits into from
Apr 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ import 'focus-visible/dist/focus-visible'

import theme from '../../theme'
import {MultiSiteProvider} from '../../contexts'
import {resolveSiteFromUrl} from '../../utils/site-utils'
import {resolveLocaleFromUrl} from '../../utils/utils'
import {resolveSiteFromUrl, resolveLocaleFromUrl} from '../../utils/site-utils'
import {getConfig} from 'pwa-kit-runtime/utils/ssr-config'
import {createUrlTemplate} from '../../utils/url'

Expand Down
10 changes: 2 additions & 8 deletions packages/template-retail-react-app/app/components/_app/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,7 @@ import {useCurrentCustomer} from '../../hooks/use-current-customer'
import {IntlProvider} from 'react-intl'

// Others
import {
watchOnlineStatus,
flatten,
mergeMatchedItems,
isServer,
resolveLocaleFromUrl
} from '../../utils/utils'
import {watchOnlineStatus, flatten, mergeMatchedItems, isServer} from '../../utils/utils'
import {getTargetLocale, fetchTranslations} from '../../utils/locale'
import {
DEFAULT_SITE_TITLE,
Expand All @@ -68,7 +62,7 @@ import {
} from '../../constants'

import Seo from '../seo'
import {resolveSiteFromUrl} from '../../utils/site-utils'
import {resolveLocaleFromUrl, resolveSiteFromUrl} from '../../utils/site-utils'

const onClient = typeof window !== 'undefined'

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,7 @@ jest.mock('react-router', () => {
})

afterEach(() => {
jest.clearAllMocks()
const originalLocation = window.location

// Restore `window.location` to the `jsdom` `Location` object
window.location = originalLocation
jest.resetModules()
})

const TestComponent = () => {
Expand Down
129 changes: 128 additions & 1 deletion packages/template-retail-react-app/app/utils/site-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
*/

import {getConfig} from 'pwa-kit-runtime/utils/ssr-config'
import {getParamsFromPath} from './utils'
import {absoluteUrl} from './url'

/**
Expand Down Expand Up @@ -95,3 +94,131 @@ export const getSiteByReference = (siteRef) => {
}) || defaultSite
)
}

/**
* This function return the identifiers (site and locale) from the given url
* The site will always go before locale if both of them are presented in the pathname
* @param path {string}
* @returns {{siteRef: string, localeRef: string}} - site and locale reference (it could either be id or alias)
*/
export const getParamsFromPath = (path) => {
const {pathname, search} = new URL(absoluteUrl(path))

const config = getConfig()
const {pathMatcher, searchMatcherForSite, searchMatcherForLocale} = getConfigMatcher(config)
const pathMatch = pathname.match(pathMatcher)
const searchMatchForSite = search.match(searchMatcherForSite)
const searchMatchForLocale = search.match(searchMatcherForLocale)

// the value can only either in the path or search query param, there will be no overridden
const siteRef = pathMatch?.groups.site || searchMatchForSite?.groups.site

const localeRef = pathMatch?.groups.locale || searchMatchForLocale?.groups.locale
return {siteRef, localeRef}
}

/**
* This function returns the url config from the current configuration
* @return {object} - url config
*/
export const getUrlConfig = () => {
const {app} = getConfig()
if (!app.url) {
throw new Error('Cannot find `url` key. Please check your configuration file.')
}
return app.url
}

/**
* Given your application's configuration this function returns a set of regular expressions used to match the site
* and locale references from an url.
* @param config
* @return {{searchMatcherForSite: RegExp, searchMatcherForLocale: RegExp, pathMatcher: RegExp}}
*/
export const getConfigMatcher = (config) => {
if (!config) {
throw new Error('Config is not defined.')
}

const allSites = getSites()
const siteIds = []
const siteAliases = []
const localesIds = []
const localeAliases = []
allSites.forEach((site) => {
siteAliases.push(site.alias)
siteIds.push(site.id)
const {l10n} = site
l10n.supportedLocales.forEach((locale) => {
localesIds.push(locale.id)
localeAliases.push(locale.alias)
})
})
const sites = [...siteIds, ...siteAliases].filter(Boolean)
const locales = [...localesIds, ...localeAliases].filter(Boolean)

// prettier-ignore
// eslint-disable-next-line
const searchPatternForSite = `site=(?<site>${sites.join('|')})`
// prettier-ignore
// eslint-disable-next-line
const pathPattern = `(?:\/(?<site>${sites.join('|')}))?(?:\/(?<locale>${locales.join("|")}))?(?!\\w)`
// prettier-ignore
// eslint-disable-next-line
const searchPatternForLocale = `locale=(?<locale>${locales.join('|')})`
const pathMatcher = new RegExp(pathPattern)
const searchMatcherForSite = new RegExp(searchPatternForSite)
const searchMatcherForLocale = new RegExp(searchPatternForLocale)
return {
pathMatcher,
searchMatcherForSite,
searchMatcherForLocale
}
}

/**
* Given a site and a locale reference, return the locale object
* @param site - site to look for the locale
* @param localeRef - the locale ref to look for in site supported locales
* @return {object|undefined}
*/
export const getLocaleByReference = (site, localeRef) => {
if (!site) {
throw new Error('Site is not defined. It is required to look for locale object')
}
return site.l10n.supportedLocales.find(
(locale) => locale.id === localeRef || locale.alias === localeRef
)
}

/**
* Determine the locale object from an url
* If the localeRef is not found from the url, set it to default locale of the current site
* and use it to find the locale object
*
* @param url
* @return {Object} locale object
*/
export const resolveLocaleFromUrl = (url) => {
if (!url) {
throw new Error('URL is required to look for the locale object')
}
let {localeRef} = getParamsFromPath(url)
const site = resolveSiteFromUrl(url)
const {supportedLocales} = site.l10n
// if no localeRef is found, use the default value of the current site
if (!localeRef) {
localeRef = site.l10n.defaultLocale
}
const locale = supportedLocales.find(
(locale) => locale.alias === localeRef || locale.id === localeRef
)
if (locale) {
return locale
}
// if locale is not defined, use default locale as fallback value
const defaultLocale = site.l10n.defaultLocale
return supportedLocales.find(
(locale) => locale.alias === defaultLocale || locale.id === defaultLocale
)
}
163 changes: 163 additions & 0 deletions packages/template-retail-react-app/app/utils/site-utils.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {getDefaultSite, getSites, resolveSiteFromUrl} from './site-utils'
import {getConfig} from 'pwa-kit-runtime/utils/ssr-config'

import mockConfig from '../../config/mocks/default'
import {getParamsFromPath, resolveLocaleFromUrl} from './site-utils'
jest.mock('pwa-kit-runtime/utils/ssr-config', () => {
const origin = jest.requireActual('pwa-kit-react-sdk/ssr/universal/utils')
return {
Expand Down Expand Up @@ -204,3 +205,165 @@ describe('getSites', function () {
}).toThrow()
})
})

describe('getParamsFromPath', function () {
const cases = [
{path: '/us/en-US/', expectedRes: {siteRef: 'us', localeRef: 'en-US'}},
{path: '/us/en-US', expectedRes: {siteRef: 'us', localeRef: 'en-US'}},
{path: '/us/en', expectedRes: {siteRef: 'us', localeRef: 'en'}},
{path: '/us/en/', expectedRes: {siteRef: 'us', localeRef: 'en'}},
{path: '/RefArch/en-US/', expectedRes: {siteRef: 'RefArch', localeRef: 'en-US'}},
{path: '/RefArch/en/', expectedRes: {siteRef: 'RefArch', localeRef: 'en'}},
{path: '/us/en-US/category/womens', expectedRes: {siteRef: 'us', localeRef: 'en-US'}},
{
path: '/RefArch/en-US/category/womens',
expectedRes: {siteRef: 'RefArch', localeRef: 'en-US'}
},
{path: '/en-US/category/womens', expectedRes: {siteRef: undefined, localeRef: 'en-US'}},
{path: '/en/category/womens', expectedRes: {siteRef: undefined, localeRef: 'en'}},
{path: '/category/womens', expectedRes: {siteRef: undefined, localeRef: undefined}},
{path: '/en/', expectedRes: {siteRef: undefined, localeRef: 'en'}},
{path: '/en', expectedRes: {siteRef: undefined, localeRef: 'en'}},
{path: '/ca/', expectedRes: {siteRef: undefined, localeRef: 'ca'}},
{path: '/ca', expectedRes: {siteRef: undefined, localeRef: 'ca'}},
{path: '/', expectedRes: {site: undefined, localeRef: undefined}},
{path: '/?site=us', expectedRes: {siteRef: 'us', localeRef: undefined}},
{path: '/?site=us&locale=en', expectedRes: {siteRef: 'us', localeRef: 'en'}},
{path: '/en-US/category/womens?site=us', expectedRes: {siteRef: 'us', localeRef: 'en-US'}},
{
path: '/us/category/womens?locale=en-US',
expectedRes: {siteRef: 'us', localeRef: 'en-US'}
},
{path: '/us/category/womens?locale=en', expectedRes: {siteRef: 'us', localeRef: 'en'}},
{
path: '/category/womens?site=us&locale=en-US',
expectedRes: {siteRef: 'us', localeRef: 'en-US'}
},
{
path: '/category/womens?site=RefArch&locale=en-US',
expectedRes: {siteRef: 'RefArch', localeRef: 'en-US'}
}
]
cases.forEach(({path, expectedRes}) => {
test(`return expected values when path is ${path}`, () => {
getConfig.mockImplementation(() => {
return {
...mockConfig,
app: {
...mockConfig.app,
sites: [
{
id: 'RefArch',
alias: 'us',
l10n: {
supportedCurrencies: ['USD'],
defaultCurrency: 'USD',
defaultLocale: 'en-US',
supportedLocales: [
{
id: 'en-US',
alias: 'en',
preferredCurrency: 'USD'
},
{
id: 'en-CA',
alias: 'ca',
preferredCurrency: 'USD'
}
]
}
},
{
id: 'RefArchGlobal',
alias: 'global',
l10n: {
supportedCurrencies: ['GBP', 'EUR', 'CNY', 'JPY'],
defaultCurrency: 'GBP',
supportedLocales: [
{
id: 'de-DE',
preferredCurrency: 'EUR'
},
{
id: 'en-GB',
alias: 'uk',
preferredCurrency: 'GBP'
}
],
defaultLocale: 'en-GB'
}
}
]
}
}
})
// getSites.mockImplementation(() => {
// return
// })
expect(getParamsFromPath(path)).toEqual(expectedRes)
})
})
})

describe('resolveLocaleFromUrl', function () {
const cases = [
{
path: '/',
expectedRes: {
id: 'en-GB',
preferredCurrency: 'GBP'
}
},
{
path: '/uk/en-GB/women/dresses',
expectedRes: {
id: 'en-GB',
preferredCurrency: 'GBP'
}
},
{
path: '/women/dresses/?site=uk&locale=en-GB',
expectedRes: {
id: 'en-GB',
preferredCurrency: 'GBP'
}
},
{
path: '/uk/fr/women/dresses',
expectedRes: {
id: 'fr-FR',
alias: 'fr',
preferredCurrency: 'EUR'
}
},
{
path: '/women/dresses/?site=uk&locale=fr',
expectedRes: {
id: 'fr-FR',
alias: 'fr',
preferredCurrency: 'EUR'
}
},
{
path: '/us/en-US/women/dresses',
expectedRes: {
id: 'en-US',
preferredCurrency: 'USD'
}
},
{
path: '/women/dresses/?site=us&locale=en-US',
expectedRes: {
id: 'en-US',
preferredCurrency: 'USD'
}
}
]
cases.forEach(({path, expectedRes}) => {
test(`returns expected locale with given path ${path}`, () => {
getConfig.mockImplementation(() => mockConfig)
const locale = resolveLocaleFromUrl(path)
expect(locale).toEqual(expectedRes)
})
})
})
8 changes: 6 additions & 2 deletions packages/template-retail-react-app/app/utils/url.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,12 @@
*/

import {getAppOrigin} from 'pwa-kit-react-sdk/utils/url'
import {getLocaleByReference, getParamsFromPath} from './utils'
import {getDefaultSite, getSiteByReference} from './site-utils'
import {
getLocaleByReference,
getParamsFromPath,
getDefaultSite,
getSiteByReference
} from './site-utils'
import {HOME_HREF, urlPartPositions} from '../constants'

/**
Expand Down
Loading