Skip to content

Commit

Permalink
Move some util to site-utils to avoid circular imports (#1133)
Browse files Browse the repository at this point in the history
* move some util to site-utils to avoid circular imports
  • Loading branch information
alexvuong authored Apr 21, 2023
1 parent 3100365 commit 72e30af
Show file tree
Hide file tree
Showing 9 changed files with 311 additions and 359 deletions.
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

0 comments on commit 72e30af

Please sign in to comment.