@openedx/frontend-base 1.0.0-alpha.0 → 1.0.0-alpha.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (232) hide show
  1. package/config/eslint/base.eslint.config.js +1 -1
  2. package/config/jest/jest.config.js +1 -0
  3. package/config/types.js +0 -2
  4. package/config/webpack/common-config/all/getStylesheetRule.js +1 -1
  5. package/config/webpack/plugins/html-webpack-new-relic-plugin/test/HtmlWebpackNewRelicPlugin.test.js +66 -0
  6. package/config/webpack/webpack.config.build.js +1 -11
  7. package/config/webpack/webpack.config.dev.js +5 -11
  8. package/config/webpack/webpack.config.dev.shell.js +5 -11
  9. package/package.json +11 -3
  10. package/runtime/__mocks__/file.js +1 -0
  11. package/runtime/__mocks__/svg.js +1 -0
  12. package/runtime/__mocks__/universal-cookie.js +6 -0
  13. package/runtime/analytics/interface.test.js +242 -0
  14. package/runtime/auth/AxiosJwtAuthService.test.jsx +1076 -0
  15. package/runtime/auth/interceptors/createRetryInterceptor.test.js +23 -0
  16. package/runtime/config/getExternalLinkUrl.test.js +76 -0
  17. package/runtime/config/index.ts +2 -3
  18. package/runtime/i18n/lib.test.js +230 -0
  19. package/runtime/index.ts +5 -0
  20. package/runtime/initialize.async.function.config.test.js +43 -0
  21. package/runtime/initialize.const.config.test.js +41 -0
  22. package/runtime/initialize.function.config.test.js +41 -0
  23. package/runtime/initialize.test.js +356 -0
  24. package/runtime/jest.config.js +1 -0
  25. package/runtime/logging/NewRelicLoggingService.test.js +214 -0
  26. package/runtime/react/AuthenticatedPageRoute.test.jsx +135 -0
  27. package/runtime/react/ErrorBoundary.test.jsx +83 -0
  28. package/runtime/react/SiteProvider.test.jsx +66 -0
  29. package/runtime/react/SiteProvider.tsx +26 -3
  30. package/runtime/react/constants.ts +3 -0
  31. package/runtime/react/hooks/index.ts +8 -0
  32. package/runtime/react/hooks/theme/index.ts +2 -0
  33. package/runtime/react/hooks/theme/useTheme.test.ts +221 -0
  34. package/runtime/react/hooks/theme/useTheme.ts +179 -0
  35. package/runtime/react/hooks/theme/useThemeConfig.test.ts +107 -0
  36. package/runtime/react/hooks/theme/useThemeConfig.ts +34 -0
  37. package/runtime/react/hooks/theme/useThemeCore.test.ts +65 -0
  38. package/runtime/react/hooks/theme/useThemeCore.ts +52 -0
  39. package/runtime/react/hooks/theme/useThemeVariants.test.ts +97 -0
  40. package/runtime/react/hooks/theme/useThemeVariants.ts +116 -0
  41. package/runtime/react/hooks/theme/useTrackColorSchemeChoice.test.ts +54 -0
  42. package/runtime/react/hooks/theme/useTrackColorSchemeChoice.ts +30 -0
  43. package/runtime/react/hooks/theme/utils.ts +11 -0
  44. package/runtime/react/hooks/useActiveRoles.ts +15 -0
  45. package/runtime/react/hooks/useActiveRouteRoleWatcher.ts +31 -0
  46. package/runtime/react/hooks/useAppConfig.ts +9 -0
  47. package/runtime/react/hooks/useAuthenticatedUser.test.tsx +41 -0
  48. package/runtime/react/hooks/useAuthenticatedUser.ts +9 -0
  49. package/runtime/react/hooks/useSiteConfig.test.tsx +13 -0
  50. package/runtime/react/hooks/useSiteConfig.ts +9 -0
  51. package/runtime/react/hooks/useSiteEvent.ts +24 -0
  52. package/runtime/react/reducers.ts +40 -0
  53. package/runtime/routing/utils.test.ts +7 -0
  54. package/runtime/scripts/GoogleAnalyticsLoader.test.ts +77 -0
  55. package/runtime/setupTest.js +0 -35
  56. package/runtime/site.config.test.tsx +33 -0
  57. package/runtime/slots/Slot.test.tsx +40 -0
  58. package/runtime/slots/layout/DefaultSlotLayout.test.tsx +31 -0
  59. package/runtime/slots/layout/hooks.test.tsx +178 -0
  60. package/runtime/slots/layout/utils.test.ts +67 -0
  61. package/runtime/slots/types.ts +1 -0
  62. package/runtime/slots/utils.test.ts +64 -0
  63. package/runtime/slots/utils.ts +28 -9
  64. package/runtime/slots/widget/iframe/hooks.ts +1 -1
  65. package/runtime/testing/initializeMockApp.test.ts +66 -0
  66. package/runtime/testing/initializeMockApp.ts +5 -0
  67. package/runtime/utils.test.js +116 -0
  68. package/shell/Logo.test.tsx +32 -0
  69. package/shell/__mocks__/file.js +1 -0
  70. package/shell/__mocks__/svg.js +1 -0
  71. package/shell/__mocks__/universal-cookie.js +6 -0
  72. package/shell/app.scss +2 -1
  73. package/shell/app.ts +14 -0
  74. package/shell/dev/devHome/app.ts +2 -2
  75. package/shell/dev/slotShowcase/app.tsx +9 -9
  76. package/shell/header/app.tsx +3 -3
  77. package/shell/jest.config.js +1 -0
  78. package/shell/router/createRouter.test.tsx +50 -0
  79. package/shell/router/getAppRoutes.test.tsx +59 -0
  80. package/shell/setupTest.js +0 -35
  81. package/shell/site.config.dev.tsx +3 -3
  82. package/shell/site.config.test.tsx +16 -0
  83. package/shell/site.tsx +1 -1
  84. package/tools/dist/cli/intl-imports.test.js +146 -0
  85. package/tools/dist/cli/openedx.js +1 -15
  86. package/tools/dist/cli/utils/printUsage.js +0 -9
  87. package/tools/dist/eslint/base.eslint.config.js +1 -1
  88. package/tools/dist/jest/jest.config.js +1 -0
  89. package/tools/dist/types.js +0 -2
  90. package/tools/dist/webpack/common-config/all/getStylesheetRule.js +1 -1
  91. package/tools/dist/webpack/plugins/html-webpack-new-relic-plugin/test/HtmlWebpackNewRelicPlugin.test.js +66 -0
  92. package/tools/dist/webpack/webpack.config.build.js +1 -11
  93. package/tools/dist/webpack/webpack.config.dev.js +5 -11
  94. package/tools/dist/webpack/webpack.config.dev.shell.js +5 -11
  95. package/types.ts +21 -1
  96. package/config/webpack/plugins/paragon-webpack-plugin/ParagonWebpackPlugin.js +0 -108
  97. package/config/webpack/plugins/paragon-webpack-plugin/index.js +0 -7
  98. package/config/webpack/plugins/paragon-webpack-plugin/utils/assetUtils.js +0 -64
  99. package/config/webpack/plugins/paragon-webpack-plugin/utils/htmlUtils.js +0 -53
  100. package/config/webpack/plugins/paragon-webpack-plugin/utils/index.js +0 -9
  101. package/config/webpack/plugins/paragon-webpack-plugin/utils/paragonStylesheetUtils.js +0 -114
  102. package/config/webpack/plugins/paragon-webpack-plugin/utils/scriptUtils.js +0 -146
  103. package/config/webpack/plugins/paragon-webpack-plugin/utils/stylesheetUtils.js +0 -126
  104. package/config/webpack/plugins/paragon-webpack-plugin/utils/tagUtils.js +0 -57
  105. package/config/webpack/types.js +0 -2
  106. package/config/webpack/utils/paragonUtils.js +0 -138
  107. package/eslint.config.js +0 -18
  108. package/frontend-base.d.ts +0 -8
  109. package/jest.config.js +0 -7
  110. package/openedx-frontend-base.tgz +0 -0
  111. package/runtime/react/hooks.ts +0 -106
  112. package/test-site/app.d.ts +0 -15
  113. package/test-site/dist/176.436443549ebb858db483.js +0 -2
  114. package/test-site/dist/176.436443549ebb858db483.js.map +0 -1
  115. package/test-site/dist/362.536eff787d2380fe246c.js +0 -2
  116. package/test-site/dist/362.536eff787d2380fe246c.js.map +0 -1
  117. package/test-site/dist/653.486966b108d224551296.js +0 -2
  118. package/test-site/dist/653.486966b108d224551296.js.map +0 -1
  119. package/test-site/dist/74e025d3fe9a7b7f8503054e2563b353.jpg +0 -0
  120. package/test-site/dist/806.323cf6496ad0a7fe73a7.js +0 -3
  121. package/test-site/dist/806.323cf6496ad0a7fe73a7.js.LICENSE.txt +0 -106
  122. package/test-site/dist/806.323cf6496ad0a7fe73a7.js.map +0 -1
  123. package/test-site/dist/95ec738c0b7faac5b5c9126794446bbd.svg +0 -4
  124. package/test-site/dist/app.612058b36c74787759ac.css +0 -61
  125. package/test-site/dist/app.612058b36c74787759ac.css.map +0 -1
  126. package/test-site/dist/app.612058b36c74787759ac.js +0 -2
  127. package/test-site/dist/app.612058b36c74787759ac.js.map +0 -1
  128. package/test-site/dist/cb28cdb1468c915e27e5cec9af64f22f.svg +0 -1
  129. package/test-site/dist/index.html +0 -1
  130. package/test-site/dist/report.html +0 -39
  131. package/test-site/dist/runtime.c7aeaf7b967496cb076f.js +0 -2
  132. package/test-site/dist/runtime.c7aeaf7b967496cb076f.js.map +0 -1
  133. package/test-site/eslint.config.js +0 -12
  134. package/test-site/package-lock.json +0 -19226
  135. package/test-site/package.json +0 -29
  136. package/test-site/public/index.html +0 -10
  137. package/test-site/site.config.build.tsx +0 -27
  138. package/test-site/site.config.dev.tsx +0 -27
  139. package/test-site/src/authenticated-page/AuthenticatedPage.tsx +0 -18
  140. package/test-site/src/authenticated-page/i18n/index.ts +0 -27
  141. package/test-site/src/authenticated-page/index.tsx +0 -28
  142. package/test-site/src/example-page/ExamplePage.tsx +0 -79
  143. package/test-site/src/example-page/Image.tsx +0 -11
  144. package/test-site/src/example-page/ParagonPreview.jsx +0 -66
  145. package/test-site/src/example-page/apple.jpg +0 -0
  146. package/test-site/src/example-page/apple.svg +0 -1
  147. package/test-site/src/example-page/index.ts +0 -16
  148. package/test-site/src/i18n/README.md +0 -3
  149. package/test-site/src/i18n/messages/frontend-app-sample/ar.json +0 -4
  150. package/test-site/src/i18n/messages/frontend-app-sample/eo.json +0 -1
  151. package/test-site/src/i18n/messages/frontend-app-sample/es_419.json +0 -4
  152. package/test-site/src/i18n/messages/frontend-component-emptylangs/ar.json +0 -1
  153. package/test-site/src/i18n/messages/frontend-component-singlelang/ar.json +0 -3
  154. package/test-site/src/iframe-widget/IframeWidget.tsx +0 -14
  155. package/test-site/src/iframe-widget/index.ts +0 -16
  156. package/test-site/src/index.tsx +0 -3
  157. package/test-site/src/messages.js +0 -11
  158. package/test-site/src/site.scss +0 -11
  159. package/test-site/tsconfig.json +0 -14
  160. package/tools/babel/babel.config.js +0 -27
  161. package/tools/babel.config.js +0 -3
  162. package/tools/cli/README.md +0 -29
  163. package/tools/cli/commands/pack.ts +0 -9
  164. package/tools/cli/commands/release.ts +0 -27
  165. package/tools/cli/commands/serve.ts +0 -43
  166. package/tools/cli/intl-imports.ts +0 -274
  167. package/tools/cli/openedx.ts +0 -101
  168. package/tools/cli/transifex-utils.ts +0 -75
  169. package/tools/cli/utils/ensureConfigFilenameOption.ts +0 -40
  170. package/tools/cli/utils/formatter.ts +0 -10
  171. package/tools/cli/utils/getResolvedConfigPath.ts +0 -23
  172. package/tools/cli/utils/prettyPrintTitle.ts +0 -15
  173. package/tools/cli/utils/printUsage.ts +0 -53
  174. package/tools/config-helpers/createConfig.ts +0 -8
  175. package/tools/config-helpers/createLintConfig.ts +0 -14
  176. package/tools/config-helpers/getBaseConfig.ts +0 -11
  177. package/tools/defaultConfigPaths.ts +0 -30
  178. package/tools/dist/cli/commands/pack.js +0 -14
  179. package/tools/dist/cli/commands/release.js +0 -28
  180. package/tools/dist/webpack/plugins/paragon-webpack-plugin/ParagonWebpackPlugin.js +0 -108
  181. package/tools/dist/webpack/plugins/paragon-webpack-plugin/index.js +0 -7
  182. package/tools/dist/webpack/plugins/paragon-webpack-plugin/utils/assetUtils.js +0 -64
  183. package/tools/dist/webpack/plugins/paragon-webpack-plugin/utils/htmlUtils.js +0 -53
  184. package/tools/dist/webpack/plugins/paragon-webpack-plugin/utils/index.js +0 -9
  185. package/tools/dist/webpack/plugins/paragon-webpack-plugin/utils/paragonStylesheetUtils.js +0 -114
  186. package/tools/dist/webpack/plugins/paragon-webpack-plugin/utils/scriptUtils.js +0 -146
  187. package/tools/dist/webpack/plugins/paragon-webpack-plugin/utils/stylesheetUtils.js +0 -126
  188. package/tools/dist/webpack/plugins/paragon-webpack-plugin/utils/tagUtils.js +0 -57
  189. package/tools/dist/webpack/types.js +0 -2
  190. package/tools/dist/webpack/utils/paragonUtils.js +0 -138
  191. package/tools/eslint/base.eslint.config.js +0 -124
  192. package/tools/eslint/modules.d.ts +0 -5
  193. package/tools/eslint.config.js +0 -15
  194. package/tools/index.ts +0 -3
  195. package/tools/jest/jest.config.js +0 -30
  196. package/tools/jest.config.js +0 -19
  197. package/tools/tsconfig.json +0 -24
  198. package/tools/types.ts +0 -21
  199. package/tools/typescript/tsconfig.json +0 -32
  200. package/tools/webpack/common-config/README.md +0 -15
  201. package/tools/webpack/common-config/all/getCodeRules.ts +0 -51
  202. package/tools/webpack/common-config/all/getFileLoaderRules.ts +0 -23
  203. package/tools/webpack/common-config/all/getIgnoreWarnings.ts +0 -13
  204. package/tools/webpack/common-config/all/getImageMinimizer.ts +0 -26
  205. package/tools/webpack/common-config/all/getStylesheetRule.ts +0 -111
  206. package/tools/webpack/common-config/dev/getDevServer.ts +0 -35
  207. package/tools/webpack/common-config/index.ts +0 -6
  208. package/tools/webpack/common-config/site/getHtmlWebpackPlugin.ts +0 -11
  209. package/tools/webpack/modules.d.ts +0 -6
  210. package/tools/webpack/plugins/html-webpack-new-relic-plugin/HtmlWebpackNewRelicPlugin.ts +0 -102
  211. package/tools/webpack/plugins/html-webpack-new-relic-plugin/LICENSE +0 -21
  212. package/tools/webpack/plugins/html-webpack-new-relic-plugin/README.md +0 -7
  213. package/tools/webpack/plugins/html-webpack-new-relic-plugin/index.js +0 -3
  214. package/tools/webpack/plugins/html-webpack-new-relic-plugin/test/fixtures/entry.js +0 -1
  215. package/tools/webpack/plugins/paragon-webpack-plugin/ParagonWebpackPlugin.ts +0 -134
  216. package/tools/webpack/plugins/paragon-webpack-plugin/index.ts +0 -3
  217. package/tools/webpack/plugins/paragon-webpack-plugin/utils/assetUtils.ts +0 -71
  218. package/tools/webpack/plugins/paragon-webpack-plugin/utils/htmlUtils.ts +0 -72
  219. package/tools/webpack/plugins/paragon-webpack-plugin/utils/index.ts +0 -6
  220. package/tools/webpack/plugins/paragon-webpack-plugin/utils/paragonStylesheetUtils.ts +0 -131
  221. package/tools/webpack/plugins/paragon-webpack-plugin/utils/scriptUtils.ts +0 -144
  222. package/tools/webpack/plugins/paragon-webpack-plugin/utils/stylesheetUtils.ts +0 -106
  223. package/tools/webpack/plugins/paragon-webpack-plugin/utils/tagUtils.ts +0 -54
  224. package/tools/webpack/types.ts +0 -69
  225. package/tools/webpack/utils/getLocalAliases.ts +0 -65
  226. package/tools/webpack/utils/getPublicPath.ts +0 -3
  227. package/tools/webpack/utils/getResolvedSiteConfigPath.ts +0 -28
  228. package/tools/webpack/utils/paragonUtils.ts +0 -152
  229. package/tools/webpack/webpack.config.build.ts +0 -93
  230. package/tools/webpack/webpack.config.dev.shell.ts +0 -122
  231. package/tools/webpack/webpack.config.dev.ts +0 -90
  232. package/tsconfig.json +0 -23
@@ -0,0 +1,23 @@
1
+ import { defaultGetBackoffMilliseconds } from './createRetryInterceptor';
2
+
3
+ describe('createRetryInterceptor: defaultGetBackoffMilliseconds', () => {
4
+ it('returns a number between 2000 and 3000 on the first retry', () => {
5
+ const backoffInMilliseconds = defaultGetBackoffMilliseconds(1);
6
+ expect(backoffInMilliseconds).toBeGreaterThanOrEqual(2000);
7
+ expect(backoffInMilliseconds).toBeLessThanOrEqual(3000);
8
+ });
9
+ it('returns a number between 4000 and 5000 on the second retry', () => {
10
+ const backoffInMilliseconds = defaultGetBackoffMilliseconds(2);
11
+ expect(backoffInMilliseconds).toBeGreaterThanOrEqual(4000);
12
+ expect(backoffInMilliseconds).toBeLessThanOrEqual(5000);
13
+ });
14
+ it('returns a number between 8000 and 9000 on the third retry', () => {
15
+ const backoffInMilliseconds = defaultGetBackoffMilliseconds(3);
16
+ expect(backoffInMilliseconds).toBeGreaterThanOrEqual(8000);
17
+ expect(backoffInMilliseconds).toBeLessThanOrEqual(9000);
18
+ });
19
+ it('returns 16000 fourth or later retry', () => {
20
+ const backoffInMilliseconds = defaultGetBackoffMilliseconds(4);
21
+ expect(backoffInMilliseconds).toEqual(16000);
22
+ });
23
+ });
@@ -0,0 +1,76 @@
1
+ import { getExternalLinkUrl, setSiteConfig } from '.';
2
+
3
+ describe('getExternalLinkUrl', () => {
4
+ afterEach(() => {
5
+ // Reset config after each test to avoid cross-test pollution
6
+ setSiteConfig({});
7
+ });
8
+
9
+ it('should return the url passed in when externalLinkUrlOverrides is not set', () => {
10
+ setSiteConfig({});
11
+ const url = 'https://foo.example.com';
12
+ expect(getExternalLinkUrl(url)).toBe(url);
13
+ });
14
+
15
+ it('should return the url passed in when externalLinkUrlOverrides does not have the url mapping', () => {
16
+ setSiteConfig({
17
+ externalLinkUrlOverrides: {
18
+ 'https://bar.example.com': 'https://mapped.example.com',
19
+ },
20
+ });
21
+ const url = 'https://foo.example.com';
22
+ expect(getExternalLinkUrl(url)).toBe(url);
23
+ });
24
+
25
+ it('should return the mapped url when externalLinkUrlOverrides has the url mapping', () => {
26
+ const url = 'https://foo.example.com';
27
+ const mappedUrl = 'https://mapped.example.com';
28
+ setSiteConfig({ externalLinkUrlOverrides: { [url]: mappedUrl } });
29
+ expect(getExternalLinkUrl(url)).toBe(mappedUrl);
30
+ });
31
+
32
+ it('should handle empty externalLinkUrlOverrides object', () => {
33
+ setSiteConfig({ externalLinkUrlOverrides: {} });
34
+ const url = 'https://foo.example.com';
35
+ expect(getExternalLinkUrl(url)).toBe(url);
36
+ });
37
+
38
+ it('should guard against empty string argument', () => {
39
+ const fallbackResult = '#';
40
+ setSiteConfig({ externalLinkUrlOverrides: { foo: 'bar' } });
41
+ expect(getExternalLinkUrl(undefined)).toBe(fallbackResult);
42
+ });
43
+
44
+ it('should guard against non-string argument', () => {
45
+ const fallbackResult = '#';
46
+ setSiteConfig({ externalLinkUrlOverrides: { foo: 'bar' } });
47
+ expect(getExternalLinkUrl(null)).toBe(fallbackResult);
48
+ expect(getExternalLinkUrl(42)).toBe(fallbackResult);
49
+ });
50
+
51
+ it('should not throw if externalLinkUrlOverrides is not an object', () => {
52
+ setSiteConfig({ externalLinkUrlOverrides: null });
53
+ const url = 'https://foo.example.com';
54
+ expect(getExternalLinkUrl(url)).toBe(url);
55
+ setSiteConfig({ externalLinkUrlOverrides: 42 });
56
+ expect(getExternalLinkUrl(url)).toBe(url);
57
+ });
58
+
59
+ it('should work with multiple mappings', () => {
60
+ setSiteConfig({
61
+ externalLinkUrlOverrides: {
62
+ 'https://a.example.com': 'https://mapped-a.example.com',
63
+ 'https://b.example.com': 'https://mapped-b.example.com',
64
+ },
65
+ });
66
+ expect(getExternalLinkUrl('https://a.example.com')).toBe(
67
+ 'https://mapped-a.example.com',
68
+ );
69
+ expect(getExternalLinkUrl('https://b.example.com')).toBe(
70
+ 'https://mapped-b.example.com',
71
+ );
72
+ expect(getExternalLinkUrl('https://c.example.com')).toBe(
73
+ 'https://c.example.com',
74
+ );
75
+ });
76
+ });
@@ -124,6 +124,7 @@ let siteConfig: SiteConfig = {
124
124
  externalRoutes: [],
125
125
  externalLinkUrlOverrides: [],
126
126
  mfeConfigApiUrl: null,
127
+ theme: {},
127
128
  accessTokenCookieName: 'edx-jwt-cookie-header-payload',
128
129
  csrfTokenApiPath: '/csrf/api/v1/token',
129
130
  ignoredErrorRegex: null,
@@ -238,9 +239,7 @@ export function getActiveRouteRoles() {
238
239
  const activeWidgetRoles: Record<string, number> = {};
239
240
 
240
241
  export function addActiveWidgetRole(role: string) {
241
- if (activeWidgetRoles[role] === undefined) {
242
- activeWidgetRoles[role] = 0;
243
- }
242
+ activeWidgetRoles[role] ??= 0;
244
243
  activeWidgetRoles[role] += 1;
245
244
  publish(ACTIVE_ROLES_CHANGED);
246
245
  }
@@ -0,0 +1,230 @@
1
+ import {
2
+ configureI18n,
3
+ getCookies,
4
+ getLocale,
5
+ getMessages,
6
+ getPrimaryLanguageSubtag,
7
+ handleRtl,
8
+ isRtl,
9
+ mergeMessages,
10
+ } from './lib';
11
+
12
+ jest.mock('universal-cookie');
13
+
14
+ describe('lib', () => {
15
+ describe('getPrimaryLanguageSubtag', () => {
16
+ it('should work for primary language subtags', () => {
17
+ expect(getPrimaryLanguageSubtag('en')).toEqual('en');
18
+ expect(getPrimaryLanguageSubtag('ars')).toEqual('ars');
19
+ expect(getPrimaryLanguageSubtag('a')).toEqual('a');
20
+ });
21
+
22
+ it('should work for longer language codes', () => {
23
+ expect(getPrimaryLanguageSubtag('en-us')).toEqual('en');
24
+ expect(getPrimaryLanguageSubtag('es-419')).toEqual('es');
25
+ expect(getPrimaryLanguageSubtag('zh-hans-CN')).toEqual('zh');
26
+ });
27
+ });
28
+
29
+ describe('getLocale', () => {
30
+ beforeEach(() => {
31
+ configureI18n({
32
+ messages: {
33
+ 'es-419': {},
34
+ de: {},
35
+ 'en-us': {},
36
+ },
37
+ });
38
+ });
39
+
40
+ it('should return a supported locale as supplied', () => {
41
+ expect(getLocale('es-419')).toEqual('es-419');
42
+ expect(getLocale('en-us')).toEqual('en-us');
43
+ });
44
+
45
+ it('should return the supported primary language tag of a not-quite-supported locale', () => {
46
+ expect(getLocale('de-de')).toEqual('de');
47
+ });
48
+
49
+ it('should return en if the locale is not supported at all', () => {
50
+ expect(getLocale('oh-no')).toEqual('en');
51
+ });
52
+
53
+ it('should look up a locale in the language preference cookie if one was not supplied', () => {
54
+ getCookies().get = jest.fn(() => 'es-419');
55
+ expect(getLocale()).toEqual('es-419');
56
+
57
+ getCookies().get = jest.fn(() => 'pl');
58
+ expect(getLocale()).toEqual('en');
59
+
60
+ getCookies().get = jest.fn(() => 'de-bah');
61
+ expect(getLocale()).toEqual('de');
62
+ });
63
+ it('should fallback to the browser locale if the cookie does not exist', () => {
64
+ getCookies().get = jest.fn(() => null);
65
+ expect(getLocale()).toEqual(global.navigator.language.toLowerCase());
66
+ });
67
+ });
68
+
69
+ describe('getMessages', () => {
70
+ beforeEach(() => {
71
+ configureI18n({
72
+ messages: {
73
+ 'es-419': { message: 'es-hah' },
74
+ de: { message: 'de-hah' },
75
+ 'en-us': { message: 'en-us-hah' },
76
+ },
77
+ });
78
+
79
+ getCookies().get = jest.fn(() => 'es-419'); // Means the cookie will be set to es-419
80
+ });
81
+
82
+ it('should return the messages for the provided locale', () => {
83
+ expect(getMessages('en-us').message).toEqual('en-us-hah');
84
+ });
85
+
86
+ it('should return the messages for the preferred locale if no argument is passed', () => {
87
+ expect(getMessages().message).toEqual('es-hah');
88
+ });
89
+ });
90
+
91
+ describe('isRtl', () => {
92
+ it('should be true for RTL languages', () => {
93
+ expect(isRtl('ar')).toBe(true);
94
+ expect(isRtl('he')).toBe(true);
95
+ expect(isRtl('fa')).toBe(true);
96
+ expect(isRtl('fa-ir')).toBe(true);
97
+ expect(isRtl('ur')).toBe(true);
98
+ });
99
+
100
+ it('should be false for anything else', () => {
101
+ expect(isRtl('en')).toBe(false);
102
+ expect(isRtl('blah')).toBe(false);
103
+ expect(isRtl('es-419')).toBe(false);
104
+ expect(isRtl('de')).toBe(false);
105
+ expect(isRtl('ru')).toBe(false);
106
+ });
107
+ });
108
+
109
+ describe('handleRtl', () => {
110
+ let setAttribute;
111
+ beforeEach(() => {
112
+ setAttribute = jest.fn();
113
+
114
+ global.document.getElementsByTagName = jest.fn(() => [
115
+ {
116
+ setAttribute,
117
+ },
118
+ ]);
119
+ });
120
+
121
+ it('should do the right thing for non-RTL languages', () => {
122
+ getCookies().get = jest.fn(() => 'es-419');
123
+ configureI18n({
124
+ messages: {
125
+ 'es-419': { message: 'es-hah' },
126
+ },
127
+ });
128
+
129
+ handleRtl();
130
+ expect(setAttribute).toHaveBeenCalledWith('dir', 'ltr');
131
+ });
132
+
133
+ it('should do the right thing for RTL languages', () => {
134
+ getCookies().get = jest.fn(() => 'ar');
135
+ configureI18n({
136
+ messages: {
137
+ ar: { message: 'ar-hah' },
138
+ },
139
+ });
140
+
141
+ handleRtl();
142
+ expect(setAttribute).toHaveBeenCalledWith('dir', 'rtl');
143
+ });
144
+ });
145
+ });
146
+
147
+ describe('mergeMessages', () => {
148
+ it('should merge objects', () => {
149
+ configureI18n({
150
+ messages: {
151
+ ar: { message: 'ar-hah' },
152
+ },
153
+ });
154
+ const result = mergeMessages({ en: { foo: 'bar' }, de: { buh: 'baz' }, jp: { gah: 'wut' } });
155
+ expect(result).toEqual({
156
+ ar: { message: 'ar-hah' },
157
+ en: { foo: 'bar' },
158
+ de: { buh: 'baz' },
159
+ jp: { gah: 'wut' },
160
+ });
161
+ });
162
+
163
+ it('should merge objects from an array', () => {
164
+ configureI18n({
165
+ messages: {
166
+ ar: { message: 'ar-hah' },
167
+ },
168
+ });
169
+ const result = mergeMessages([{ foo: 'bar' }, { buh: 'baz' }, { gah: 'wut' }]);
170
+ expect(result).toEqual({
171
+ ar: { message: 'ar-hah' },
172
+ foo: 'bar',
173
+ buh: 'baz',
174
+ gah: 'wut',
175
+ });
176
+ });
177
+
178
+ it('should merge nested objects from an array', () => {
179
+ configureI18n({
180
+ messages: {
181
+ en: { init: 'initial' },
182
+ es: { init: 'inicial' },
183
+ },
184
+ });
185
+ const messages = [
186
+ {
187
+ en: { hello: 'hello' },
188
+ es: { hello: 'hola' },
189
+ },
190
+ {
191
+ en: { goodbye: 'goodbye' },
192
+ es: { goodbye: 'adiós' },
193
+ },
194
+ ];
195
+
196
+ const result = mergeMessages(messages);
197
+ expect(result).toEqual({
198
+ en: {
199
+ init: 'initial',
200
+ hello: 'hello',
201
+ goodbye: 'goodbye',
202
+ },
203
+ es: {
204
+ init: 'inicial',
205
+ hello: 'hola',
206
+ goodbye: 'adiós',
207
+ },
208
+ });
209
+ });
210
+
211
+ it('should return an empty object if no messages', () => {
212
+ configureI18n({
213
+ messages: {},
214
+ });
215
+ expect(mergeMessages(undefined)).toEqual({});
216
+ expect(mergeMessages(null)).toEqual({});
217
+ expect(mergeMessages([])).toEqual({});
218
+ expect(mergeMessages({})).toEqual({});
219
+ });
220
+
221
+ it('should return the original object if no messages', () => {
222
+ configureI18n({
223
+ messages: { en: { hello: 'world ' } },
224
+ });
225
+ expect(mergeMessages(undefined)).toEqual({ en: { hello: 'world ' } });
226
+ expect(mergeMessages(null)).toEqual({ en: { hello: 'world ' } });
227
+ expect(mergeMessages([])).toEqual({ en: { hello: 'world ' } });
228
+ expect(mergeMessages({})).toEqual({ en: { hello: 'world ' } });
229
+ });
230
+ });
package/runtime/index.ts CHANGED
@@ -110,6 +110,11 @@ export {
110
110
  useAppConfig
111
111
  } from './react';
112
112
 
113
+ export {
114
+ getUrlByRouteRole,
115
+ isRoleRouteObject
116
+ } from './routing';
117
+
113
118
  export {
114
119
  clearAllSubscriptions,
115
120
  publish,
@@ -0,0 +1,43 @@
1
+ import {
2
+ ensureAuthenticatedUser,
3
+ fetchAuthenticatedUser,
4
+ hydrateAuthenticatedUser,
5
+ } from './auth';
6
+ import { getSiteConfig } from './config';
7
+ import { initialize } from './initialize';
8
+ import {
9
+ logError,
10
+ } from './logging';
11
+ import { clearAllSubscriptions } from './subscriptions';
12
+
13
+ jest.mock('./logging');
14
+ jest.mock('./auth');
15
+ jest.mock('./analytics');
16
+ jest.mock('./i18n');
17
+ jest.mock('./auth/LocalForageCache');
18
+ jest.mock('site.config', () => async () => new Promise((resolve) => {
19
+ resolve({
20
+ JS_FILE_VAR: 'JS_FILE_VAR_VALUE_ASYNC_FUNCTION',
21
+ });
22
+ }));
23
+
24
+ let config = null;
25
+
26
+ describe('initialize with async function js file config', () => {
27
+ beforeEach(() => {
28
+ jest.resetModules();
29
+ config = getSiteConfig();
30
+ fetchAuthenticatedUser.mockReset();
31
+ ensureAuthenticatedUser.mockReset();
32
+ hydrateAuthenticatedUser.mockReset();
33
+ logError.mockReset();
34
+ clearAllSubscriptions();
35
+ });
36
+
37
+ it('should initialize the app with async function javascript file configuration', async () => {
38
+ const messages = { i_am: 'a message' };
39
+ await initialize({ messages });
40
+
41
+ expect(config.JS_FILE_VAR).toEqual('JS_FILE_VAR_VALUE_ASYNC_FUNCTION');
42
+ });
43
+ });
@@ -0,0 +1,41 @@
1
+ import {
2
+ ensureAuthenticatedUser,
3
+ fetchAuthenticatedUser,
4
+ hydrateAuthenticatedUser,
5
+ } from './auth';
6
+ import { getSiteConfig } from './config';
7
+ import { initialize } from './initialize';
8
+ import {
9
+ logError,
10
+ } from './logging';
11
+ import { clearAllSubscriptions } from './subscriptions';
12
+
13
+ jest.mock('./logging');
14
+ jest.mock('./auth');
15
+ jest.mock('./analytics');
16
+ jest.mock('./i18n');
17
+ jest.mock('./auth/LocalForageCache');
18
+ jest.mock('site.config', () => ({
19
+ JS_FILE_VAR: 'JS_FILE_VAR_VALUE_CONSTANT',
20
+ }));
21
+
22
+ let config = null;
23
+
24
+ describe('initialize with constant js file config', () => {
25
+ beforeEach(() => {
26
+ jest.resetModules();
27
+ config = getSiteConfig();
28
+ fetchAuthenticatedUser.mockReset();
29
+ ensureAuthenticatedUser.mockReset();
30
+ hydrateAuthenticatedUser.mockReset();
31
+ logError.mockReset();
32
+ clearAllSubscriptions();
33
+ });
34
+
35
+ it('should initialize the app with javascript file configuration', async () => {
36
+ const messages = { i_am: 'a message' };
37
+ await initialize({ messages });
38
+
39
+ expect(config.JS_FILE_VAR).toEqual('JS_FILE_VAR_VALUE_CONSTANT');
40
+ });
41
+ });
@@ -0,0 +1,41 @@
1
+ import {
2
+ ensureAuthenticatedUser,
3
+ fetchAuthenticatedUser,
4
+ hydrateAuthenticatedUser,
5
+ } from './auth';
6
+ import { getSiteConfig } from './config';
7
+ import { initialize } from './initialize';
8
+ import {
9
+ logError,
10
+ } from './logging';
11
+ import { clearAllSubscriptions } from './subscriptions';
12
+
13
+ jest.mock('./logging');
14
+ jest.mock('./auth');
15
+ jest.mock('./analytics');
16
+ jest.mock('./i18n');
17
+ jest.mock('./auth/LocalForageCache');
18
+ jest.mock('site.config', () => () => ({
19
+ JS_FILE_VAR: 'JS_FILE_VAR_VALUE_FUNCTION',
20
+ }));
21
+
22
+ let config = null;
23
+
24
+ describe('initialize with function js file config', () => {
25
+ beforeEach(() => {
26
+ jest.resetModules();
27
+ config = getSiteConfig();
28
+ fetchAuthenticatedUser.mockReset();
29
+ ensureAuthenticatedUser.mockReset();
30
+ hydrateAuthenticatedUser.mockReset();
31
+ logError.mockReset();
32
+ clearAllSubscriptions();
33
+ });
34
+
35
+ it('should initialize the app with javascript file configuration', async () => {
36
+ const messages = { i_am: 'a message' };
37
+ await initialize({ messages });
38
+
39
+ expect(config.JS_FILE_VAR).toEqual('JS_FILE_VAR_VALUE_FUNCTION');
40
+ });
41
+ });