diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e2316ac5a..afb48288a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added support for tax calculation where the values from customer_tax_class_ids is used - @resubaka (#3245) - Added loading product attributes (`entities.productListWithChildren.includeFields`) on category page - @andrzejewsky (#3220) - Added config to set Cache-Control header for static assets based on mime type - @phoenix-bjoern (#3268) +- Added test:unit:watch with a workaround of a jest problem with template strings - @resubaka (#3351) +- Added test to multistore.ts so it is nearly fully unit tested - @resubaka (#3352) - Added test:unit:watch with a workaround of a jest problem with template strings - @resubaka (#3351, #3354) ### Fixed diff --git a/core/lib/multistore.ts b/core/lib/multistore.ts index 6876989e50..5bc1e7def4 100644 --- a/core/lib/multistore.ts +++ b/core/lib/multistore.ts @@ -164,11 +164,32 @@ export function removeStoreCodeFromRoute (matchedRouteOrUrl: LocalizedRoute | st } } +function removeURLQueryParameter (url, parameter) { + // prefer to use l.search if you have a location/link object + var urlparts = url.split('?'); + if (urlparts.length >= 2) { + var prefix = encodeURIComponent(parameter) + '='; + var pars = urlparts[1].split(/[&;]/g); + + // reverse iteration as may be destructive + for (var i = pars.length; i-- > 0;) { + // idiom for string.startsWith + if (pars[i].lastIndexOf(prefix, 0) !== -1) { + pars.splice(i, 1); + } + } + + return urlparts[0] + (pars.length > 0 ? '?' + pars.join('&') : ''); + } + return url; +} + export function adjustMultistoreApiUrl (url: string): string { - const storeView = currentStoreView() - if (storeView.storeCode) { + const { storeCode } = currentStoreView() + if (storeCode) { + url = removeURLQueryParameter(url, 'storeCode') const urlSep = (url.indexOf('?') > 0) ? '&' : '?' - url += urlSep + 'storeCode=' + storeView.storeCode + url += `${urlSep}storeCode=${storeCode}` } return url } @@ -177,7 +198,7 @@ export function localizedDispatcherRoute (routeObj: LocalizedRoute | string, sto const appendStoreCodePrefix = config.storeViews[storeCode] ? config.storeViews[storeCode].appendStoreCode : false if (typeof routeObj === 'string') { - return appendStoreCodePrefix ? '/' + storeCode + routeObj : routeObj + return appendStoreCodePrefix ? `/${storeCode}${routeObj}` : routeObj } if (routeObj && routeObj.fullPath) { // case of using dispatcher diff --git a/core/lib/test/unit/multistore.spec.ts b/core/lib/test/unit/multistore.spec.ts index ff0745f6f0..39f362f184 100644 --- a/core/lib/test/unit/multistore.spec.ts +++ b/core/lib/test/unit/multistore.spec.ts @@ -1,6 +1,15 @@ -import { storeCodeFromRoute, prepareStoreView } from '@vue-storefront/core/lib/multistore' +import { + storeCodeFromRoute, + prepareStoreView, + adjustMultistoreApiUrl, + localizedDispatcherRoute, + LocalizedRoute, + setupMultistoreRoutes +} from '@vue-storefront/core/lib/multistore' import config from 'config' import rootStore from '@vue-storefront/core/store'; +import VueRouter, { RouteConfig } from 'vue-router' +import { RouterManager } from '@vue-storefront/core/lib/router-manager' jest.mock('@vue-storefront/core/app', () => ({ createApp: jest.fn() })) jest.mock('../../../store', () => ({})) @@ -10,9 +19,10 @@ jest.mock('@vue-storefront/core/hooks', () => ({ coreHooksExecutors: { beforeStoreViewChange: jest.fn(args => args), afterStoreViewChange: jest.fn(args => args) }})) -jest.mock('query-string', () => jest.fn()) jest.mock('@vue-storefront/core/lib/router-manager', () => ({ - RouterManager: {} + RouterManager: { + addRoutes: jest.fn() + } })) jest.mock('@vue-storefront/core/lib/logger', () => ({ Logger: {} @@ -20,6 +30,7 @@ jest.mock('@vue-storefront/core/lib/logger', () => ({ jest.mock('config', () => ({})) describe('Multistore', () => { + let router beforeEach(() => { jest.clearAllMocks(); (rootStore as any).state = {}; @@ -393,4 +404,196 @@ describe('Multistore', () => { }) }) }) + + describe('adjustMultistoreApiUrl', () => { + it('returns URL /test without storeCode as parameter', () => { + rootStore.state.storeView = { + storeCode: null + } + + expect(adjustMultistoreApiUrl('/test')).toStrictEqual('/test') + }) + + it('returns URL /test with storeCode de as parameter', () => { + rootStore.state.storeView = { + storeCode: 'de' + } + + expect(adjustMultistoreApiUrl('/test')).toStrictEqual('/test?storeCode=de') + }) + + it('returns URL /test?a=b with storeCode de as parameter and current parameters from the URL', () => { + rootStore.state.storeView = { + storeCode: 'de' + } + + expect(adjustMultistoreApiUrl('/test?a=b')).toStrictEqual('/test?a=b&storeCode=de') + }) + + it('returns URL /test?a=b&storeCode=de with added storeCode at as parameter and removes previous storeCode parameter', () => { + rootStore.state.storeView = { + storeCode: 'at' + } + + expect(adjustMultistoreApiUrl('/test?a=b&storeCode=de')).toStrictEqual('/test?a=b&storeCode=at') + }) + + it('returns URL /test?storeCode=de with changed storeCode at as parameter', () => { + rootStore.state.storeView = { + storeCode: 'at' + } + + expect(adjustMultistoreApiUrl('/test?storeCode=de')).toStrictEqual('/test?storeCode=at') + }) + + it('returns URL /test?storeCode=de with changed storeCode at as parameter', () => { + rootStore.state.storeView = { + storeCode: 'at' + } + + expect(adjustMultistoreApiUrl('/test?storeCode=de&storeCode=de')).toStrictEqual('/test?storeCode=at') + }) + }) + + describe('localizedDispatcherRoute', () => { + it('URL /test stays the same', () => { + config.storeViews = {} + + expect(localizedDispatcherRoute('/test', 'de')).toStrictEqual('/test') + }) + + it('URL /test starts with /de', () => { + config.storeViews = { + de: { + appendStoreCode: true + } + } + + expect(localizedDispatcherRoute('/test', 'de')).toStrictEqual('/de/test') + }) + + it('URL /test?a=b&b=a stays the same', () => { + config.storeViews = {} + + expect(localizedDispatcherRoute('/test?a=b&b=a', 'de')).toStrictEqual('/test?a=b&b=a') + }) + + it('URL /test?a=b&b=a starts with /de', () => { + config.storeViews = { + de: { + appendStoreCode: true + } + } + + expect(localizedDispatcherRoute('/test?a=b&b=a', 'de')).toStrictEqual('/de/test?a=b&b=a') + }) + + it('URL with LocalizedRoute object with fullPath test gets prefixed with /de', () => { + config.storeViews = {} + + const LocalizedRoute: LocalizedRoute = { + fullPath: 'test' + } + + expect(localizedDispatcherRoute(LocalizedRoute, 'de')).toStrictEqual('/test') + }) + + it('URL with LocalizedRoute object with fullPath and parameter test stays the same', () => { + config.storeViews = {} + + const LocalizedRoute: LocalizedRoute = { + fullPath: 'test', + params: { + a: 'b', + b: 'a' + } + } + + expect(localizedDispatcherRoute(LocalizedRoute, 'de')).toStrictEqual('/test?a=b&b=a') + }) + + it('URL with LocalizedRoute object with fullPath test gets prefixed with /de', () => { + config.storeViews = { + de: { + appendStoreCode: true + } + } + + const LocalizedRoute: LocalizedRoute = { + fullPath: 'test' + } + + expect(localizedDispatcherRoute(LocalizedRoute, 'de')).toStrictEqual('/de/test') + }) + + it('URL with LocalizedRoute object with fullPath test and params gets prefixed with /de', () => { + config.storeViews = { + de: { + appendStoreCode: true + } + } + + const LocalizedRoute: LocalizedRoute = { + fullPath: 'test', + params: { + a: 'b', + b: 'a' + } + } + + expect(localizedDispatcherRoute(LocalizedRoute, 'de')).toStrictEqual('/de/test?a=b&b=a') + }) + }) + + describe('setupMultistoreRoutes', () => { + it('Add new routes for each store in mapStoreUrlsFor', () => { + config.storeViews = { + 'de': { + appendStoreCode: true + }, + mapStoreUrlsFor: [ + 'de' + ], + multistore: true + } + + const routeConfig: RouteConfig[] = [ + { + path: 'test' + }, + { + path: 'test2' + } + ] + + const vueRouter = {} + + setupMultistoreRoutes(config, (vueRouter as VueRouter), routeConfig) + + expect(RouterManager.addRoutes).toBeCalledTimes(1) + }) + + it('Do nothing as mapStoreUrlsFor is empty', () => { + config.storeViews = { + 'de': { + }, + mapStoreUrlsFor: [] + } + + const routeConfig: RouteConfig[] = [ + { + path: 'test' + }, + { + path: 'test2' + } + ] + + const vueRouter = {} + + setupMultistoreRoutes(config, (vueRouter as VueRouter), routeConfig) + + expect(RouterManager.addRoutes).toBeCalledTimes(0) + }) + }) })