Skip to content

Commit 12cb5bc

Browse files
authored
fix: manage if the BASE_URL does not have the protocol for themes urls (#779)
* fix: manage if the BASE_URL does not have the protocol for themes urls * test: update test urls with the validated protocol * fix: update regex to match all passible values of BASE_URL
1 parent 52a8f2d commit 12cb5bc

File tree

5 files changed

+66
-19
lines changed

5 files changed

+66
-19
lines changed

src/react/hooks/paragon/useParagonThemeCore.test.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,8 @@ describe('useParagonThemeCore', () => {
5151

5252
const createdLinkTag = document.head.querySelector('link[data-paragon-theme-core="true"]');
5353
const createdBrandLinkTag = document.head.querySelector('link[data-brand-theme-core="true"]');
54-
const defaultFallbackUrl = `${getConfig().BASE_URL}/${PARAGON_THEME.paragon.themeUrls.core.fileName}`;
55-
const brandFallbackUrl = `${getConfig().BASE_URL}/${PARAGON_THEME.brand.themeUrls.core.fileName}`;
54+
const defaultFallbackUrl = `//${getConfig().BASE_URL}/${PARAGON_THEME.paragon.themeUrls.core.fileName}`;
55+
const brandFallbackUrl = `//${getConfig().BASE_URL}/${PARAGON_THEME.brand.themeUrls.core.fileName}`;
5656

5757
act(() => { createdLinkTag.onerror(); createdBrandLinkTag.onerror(); });
5858

@@ -61,17 +61,17 @@ describe('useParagonThemeCore', () => {
6161
expect(logInfo).toHaveBeenCalledTimes(2);
6262
expect(logInfo).toHaveBeenCalledWith(`Could not load core theme CSS from ${coreConfig.themeCore.urls.default}. Falling back to locally installed core theme CSS: ${defaultFallbackUrl}`);
6363
expect(logInfo).toHaveBeenCalledWith(`Could not load core theme CSS from ${coreConfig.themeCore.urls.brandOverride}. Falling back to locally installed core theme CSS: ${brandFallbackUrl}`);
64-
expect(fallbackLinks[0].href).toBe(defaultFallbackUrl);
65-
expect(fallbackLinks[1].href).toBe(brandFallbackUrl);
64+
expect(fallbackLinks[0].href).toBe(`http:${defaultFallbackUrl}`);
65+
expect(fallbackLinks[1].href).toBe(`http:${brandFallbackUrl}`);
6666
});
6767
it('should dispatch a log error if the fallback url is not loaded (either default or brandOverride)', () => {
6868
coreConfig.themeCore.urls.brandOverride = 'https://cdn.jsdelivr.net/npm/@edx/[email protected]/dist/core.min.css';
6969

7070
renderHook(() => useParagonThemeCore(coreConfig));
7171
const createdLinkTag = document.head.querySelector('link[data-paragon-theme-core="true"]');
7272
const createdBrandLinkTag = document.head.querySelector('link[data-brand-theme-core="true"]');
73-
const defaultFallbackUrl = `${getConfig().BASE_URL}/${PARAGON_THEME.paragon.themeUrls.core.fileName}`;
74-
const brandFallbackUrl = `${getConfig().BASE_URL}/${PARAGON_THEME.brand.themeUrls.core.fileName}`;
73+
const defaultFallbackUrl = `http://${getConfig().BASE_URL}/${PARAGON_THEME.paragon.themeUrls.core.fileName}`;
74+
const brandFallbackUrl = `http://${getConfig().BASE_URL}/${PARAGON_THEME.brand.themeUrls.core.fileName}`;
7575

7676
act(() => { createdLinkTag.onerror(); createdBrandLinkTag.onerror(); });
7777
const fallbackLinks = document.querySelectorAll('link');

src/react/hooks/paragon/useParagonThemeUrls.test.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ describe('useParagonThemeUrls', () => {
77
beforeEach(() => { jest.resetAllMocks(); });
88
it.each([
99
[undefined, undefined],
10-
[{}, { core: { urls: { default: 'localhost:8080/core.min.css', brandOverride: undefined } }, defaults: { light: 'light' }, variants: { light: { urls: { default: 'localhost:8080/light.min.css' } } } }],
10+
[{}, { core: { urls: { default: '//localhost:8080/core.min.css', brandOverride: undefined } }, defaults: { light: 'light' }, variants: { light: { urls: { default: '//localhost:8080/light.min.css' } } } }],
1111
])('handles when `config.PARAGON_THEME_URLS` is not present (%s)', (paragonThemeUrls, expectedURLConfig) => {
1212
mergeConfig({ PARAGON_THEME_URLS: paragonThemeUrls });
1313
const { result } = renderHook(() => useParagonThemeUrls());
@@ -122,7 +122,7 @@ describe('useParagonThemeUrls', () => {
122122
expect.objectContaining({
123123
core: {
124124
urls: {
125-
default: 'localhost:8080/core.min.css',
125+
default: '//localhost:8080/core.min.css',
126126
brandOverride: 'brand-core.css',
127127
},
128128
},
@@ -132,7 +132,7 @@ describe('useParagonThemeUrls', () => {
132132
variants: {
133133
light: {
134134
urls: {
135-
default: 'localhost:8080/light.min.css',
135+
default: '//localhost:8080/light.min.css',
136136
},
137137
},
138138
},

src/react/hooks/paragon/useParagonThemeVariants.test.js

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,9 @@ describe('useParagonThemeVariants', () => {
5151
const currentThemeVariant = 'light';
5252

5353
renderHook(() => useParagonThemeVariants({ themeVariants, currentThemeVariant, onComplete: themeOnComplete }));
54-
5554
const themeLinks = document.head.querySelectorAll('link');
56-
const paragonFallbackURL = `${getConfig().BASE_URL}/${PARAGON_THEME.paragon.themeUrls.variants.light.fileName}`;
57-
const brandFallbackURL = `${getConfig().BASE_URL}/${PARAGON_THEME.brand.themeUrls.variants.light.fileName}`;
55+
const paragonFallbackURL = `//${getConfig().BASE_URL}/${PARAGON_THEME.paragon.themeUrls.variants.light.fileName}`;
56+
const brandFallbackURL = `//${getConfig().BASE_URL}/${PARAGON_THEME.brand.themeUrls.variants.light.fileName}`;
5857

5958
act(() => { themeLinks.forEach((link) => link.onerror()); });
6059

@@ -65,8 +64,8 @@ describe('useParagonThemeVariants', () => {
6564
const fallbackLinkTag = document.querySelectorAll('link');
6665

6766
expect(fallbackLinkTag.length).toBe(2);
68-
expect(fallbackLinkTag[0].href).toBe(paragonFallbackURL);
69-
expect(fallbackLinkTag[1].href).toBe(brandFallbackURL);
67+
expect(fallbackLinkTag[0].href).toBe(`http:${paragonFallbackURL}`);
68+
expect(fallbackLinkTag[1].href).toBe(`http:${brandFallbackURL}`);
7069
});
7170

7271
it('should dispatch a log error if the fallback url is not loaded (either default or brandOverride)', () => {
@@ -83,14 +82,14 @@ describe('useParagonThemeVariants', () => {
8382

8483
renderHook(() => useParagonThemeVariants({ themeVariants, currentThemeVariant, onComplete: themeOnComplete }));
8584
const themeLinks = document.head.querySelectorAll('link');
86-
const paragonFallbackURL = `${getConfig().BASE_URL}/${PARAGON_THEME.paragon.themeUrls.variants.light.fileName}`;
87-
const brandFallbackURL = `${getConfig().BASE_URL}/${PARAGON_THEME.brand.themeUrls.variants.light.fileName}`;
85+
const paragonFallbackURL = `//${getConfig().BASE_URL}/${PARAGON_THEME.paragon.themeUrls.variants.light.fileName}`;
86+
const brandFallbackURL = `//${getConfig().BASE_URL}/${PARAGON_THEME.brand.themeUrls.variants.light.fileName}`;
8887

8988
act(() => { themeLinks.forEach((link) => link.onerror()); });
9089

9190
const fallbackLinks = document.querySelectorAll('link');
92-
expect(fallbackLinks[0].href).toBe(paragonFallbackURL);
93-
expect(fallbackLinks[1].href).toBe(brandFallbackURL);
91+
expect(fallbackLinks[0].href).toBe(`http:${paragonFallbackURL}`);
92+
expect(fallbackLinks[1].href).toBe(`http:${brandFallbackURL}`);
9493
act(() => { fallbackLinks.forEach((link) => link.onerror()); });
9594

9695
expect(logInfo).toHaveBeenCalledTimes(2);

src/react/hooks/paragon/utils.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,12 @@ export const removeExistingLinks = (existingLinks) => {
1818
*/
1919
export const fallbackThemeUrl = (url) => {
2020
const baseUrl = getConfig().BASE_URL || window.location?.origin;
21-
return `${baseUrl}${basename}${url}`;
21+
22+
// validates if the baseurl has the protocol to be interpreted correctly by the browser,
23+
// if is not present add '//' to use Protocol-relative URL
24+
const protocol = /^(https?:)?\/\//.test(baseUrl) ? '' : '//';
25+
26+
return `${protocol}${baseUrl}${basename}${url}`;
2227
};
2328

2429
export const isEmptyObject = (obj) => !obj || Object.keys(obj).length === 0;

src/react/hooks/paragon/utils.test.js

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { fallbackThemeUrl } from './utils';
2+
import { mergeConfig } from '../../../config';
3+
4+
describe('fallbackThemeUrl', () => {
5+
it('if should return a relative url if the BASE_URL does not provide the protocol', () => {
6+
mergeConfig({
7+
BASE_URL: 'example.com',
8+
});
9+
10+
expect(fallbackThemeUrl('my.css')).toBe('//example.com/my.css');
11+
});
12+
13+
it('if should return a relative url if the BASE_URL is a relative url', () => {
14+
mergeConfig({
15+
BASE_URL: '//example.com',
16+
});
17+
expect(fallbackThemeUrl('my.css')).toBe('//example.com/my.css');
18+
});
19+
20+
it('if should return a full url if the BASE_URL provides the protocol', () => {
21+
mergeConfig({
22+
BASE_URL: 'http://example.com',
23+
});
24+
expect(fallbackThemeUrl('my.css')).toBe('http://example.com/my.css');
25+
26+
mergeConfig({
27+
BASE_URL: 'https://example.com',
28+
});
29+
expect(fallbackThemeUrl('my.css')).toBe('https://example.com/my.css');
30+
});
31+
32+
it('if should return a full url base on the window location if BASE_URL is not defined', () => {
33+
mergeConfig({
34+
BASE_URL: 'http://example.com',
35+
});
36+
expect(fallbackThemeUrl('my.css')).toBe('http://example.com/my.css');
37+
38+
mergeConfig({
39+
BASE_URL: '',
40+
});
41+
expect(fallbackThemeUrl('my.css')).toBe('http://localhost/my.css');
42+
});
43+
});

0 commit comments

Comments
 (0)