@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.
- package/config/eslint/base.eslint.config.js +1 -1
- package/config/jest/jest.config.js +1 -0
- package/config/types.js +0 -2
- package/config/webpack/common-config/all/getStylesheetRule.js +1 -1
- package/config/webpack/plugins/html-webpack-new-relic-plugin/test/HtmlWebpackNewRelicPlugin.test.js +66 -0
- package/config/webpack/webpack.config.build.js +1 -11
- package/config/webpack/webpack.config.dev.js +5 -11
- package/config/webpack/webpack.config.dev.shell.js +5 -11
- package/package.json +11 -3
- package/runtime/__mocks__/file.js +1 -0
- package/runtime/__mocks__/svg.js +1 -0
- package/runtime/__mocks__/universal-cookie.js +6 -0
- package/runtime/analytics/interface.test.js +242 -0
- package/runtime/auth/AxiosJwtAuthService.test.jsx +1076 -0
- package/runtime/auth/interceptors/createRetryInterceptor.test.js +23 -0
- package/runtime/config/getExternalLinkUrl.test.js +76 -0
- package/runtime/config/index.ts +2 -3
- package/runtime/i18n/lib.test.js +230 -0
- package/runtime/index.ts +5 -0
- package/runtime/initialize.async.function.config.test.js +43 -0
- package/runtime/initialize.const.config.test.js +41 -0
- package/runtime/initialize.function.config.test.js +41 -0
- package/runtime/initialize.test.js +356 -0
- package/runtime/jest.config.js +1 -0
- package/runtime/logging/NewRelicLoggingService.test.js +214 -0
- package/runtime/react/AuthenticatedPageRoute.test.jsx +135 -0
- package/runtime/react/ErrorBoundary.test.jsx +83 -0
- package/runtime/react/SiteProvider.test.jsx +66 -0
- package/runtime/react/SiteProvider.tsx +26 -3
- package/runtime/react/constants.ts +3 -0
- package/runtime/react/hooks/index.ts +8 -0
- package/runtime/react/hooks/theme/index.ts +2 -0
- package/runtime/react/hooks/theme/useTheme.test.ts +221 -0
- package/runtime/react/hooks/theme/useTheme.ts +179 -0
- package/runtime/react/hooks/theme/useThemeConfig.test.ts +107 -0
- package/runtime/react/hooks/theme/useThemeConfig.ts +34 -0
- package/runtime/react/hooks/theme/useThemeCore.test.ts +65 -0
- package/runtime/react/hooks/theme/useThemeCore.ts +52 -0
- package/runtime/react/hooks/theme/useThemeVariants.test.ts +97 -0
- package/runtime/react/hooks/theme/useThemeVariants.ts +116 -0
- package/runtime/react/hooks/theme/useTrackColorSchemeChoice.test.ts +54 -0
- package/runtime/react/hooks/theme/useTrackColorSchemeChoice.ts +30 -0
- package/runtime/react/hooks/theme/utils.ts +11 -0
- package/runtime/react/hooks/useActiveRoles.ts +15 -0
- package/runtime/react/hooks/useActiveRouteRoleWatcher.ts +31 -0
- package/runtime/react/hooks/useAppConfig.ts +9 -0
- package/runtime/react/hooks/useAuthenticatedUser.test.tsx +41 -0
- package/runtime/react/hooks/useAuthenticatedUser.ts +9 -0
- package/runtime/react/hooks/useSiteConfig.test.tsx +13 -0
- package/runtime/react/hooks/useSiteConfig.ts +9 -0
- package/runtime/react/hooks/useSiteEvent.ts +24 -0
- package/runtime/react/reducers.ts +40 -0
- package/runtime/routing/utils.test.ts +7 -0
- package/runtime/scripts/GoogleAnalyticsLoader.test.ts +77 -0
- package/runtime/setupTest.js +0 -35
- package/runtime/site.config.test.tsx +33 -0
- package/runtime/slots/Slot.test.tsx +40 -0
- package/runtime/slots/layout/DefaultSlotLayout.test.tsx +31 -0
- package/runtime/slots/layout/hooks.test.tsx +178 -0
- package/runtime/slots/layout/utils.test.ts +67 -0
- package/runtime/slots/types.ts +1 -0
- package/runtime/slots/utils.test.ts +64 -0
- package/runtime/slots/utils.ts +28 -9
- package/runtime/slots/widget/iframe/hooks.ts +1 -1
- package/runtime/testing/initializeMockApp.test.ts +66 -0
- package/runtime/testing/initializeMockApp.ts +5 -0
- package/runtime/utils.test.js +116 -0
- package/shell/Logo.test.tsx +32 -0
- package/shell/__mocks__/file.js +1 -0
- package/shell/__mocks__/svg.js +1 -0
- package/shell/__mocks__/universal-cookie.js +6 -0
- package/shell/app.scss +2 -1
- package/shell/app.ts +14 -0
- package/shell/dev/devHome/app.ts +2 -2
- package/shell/dev/slotShowcase/app.tsx +9 -9
- package/shell/header/app.tsx +3 -3
- package/shell/jest.config.js +1 -0
- package/shell/router/createRouter.test.tsx +50 -0
- package/shell/router/getAppRoutes.test.tsx +59 -0
- package/shell/setupTest.js +0 -35
- package/shell/site.config.dev.tsx +3 -3
- package/shell/site.config.test.tsx +16 -0
- package/shell/site.tsx +1 -1
- package/tools/dist/cli/intl-imports.test.js +146 -0
- package/tools/dist/cli/openedx.js +1 -15
- package/tools/dist/cli/utils/printUsage.js +0 -9
- package/tools/dist/eslint/base.eslint.config.js +1 -1
- package/tools/dist/jest/jest.config.js +1 -0
- package/tools/dist/types.js +0 -2
- package/tools/dist/webpack/common-config/all/getStylesheetRule.js +1 -1
- package/tools/dist/webpack/plugins/html-webpack-new-relic-plugin/test/HtmlWebpackNewRelicPlugin.test.js +66 -0
- package/tools/dist/webpack/webpack.config.build.js +1 -11
- package/tools/dist/webpack/webpack.config.dev.js +5 -11
- package/tools/dist/webpack/webpack.config.dev.shell.js +5 -11
- package/types.ts +21 -1
- package/config/webpack/plugins/paragon-webpack-plugin/ParagonWebpackPlugin.js +0 -108
- package/config/webpack/plugins/paragon-webpack-plugin/index.js +0 -7
- package/config/webpack/plugins/paragon-webpack-plugin/utils/assetUtils.js +0 -64
- package/config/webpack/plugins/paragon-webpack-plugin/utils/htmlUtils.js +0 -53
- package/config/webpack/plugins/paragon-webpack-plugin/utils/index.js +0 -9
- package/config/webpack/plugins/paragon-webpack-plugin/utils/paragonStylesheetUtils.js +0 -114
- package/config/webpack/plugins/paragon-webpack-plugin/utils/scriptUtils.js +0 -146
- package/config/webpack/plugins/paragon-webpack-plugin/utils/stylesheetUtils.js +0 -126
- package/config/webpack/plugins/paragon-webpack-plugin/utils/tagUtils.js +0 -57
- package/config/webpack/types.js +0 -2
- package/config/webpack/utils/paragonUtils.js +0 -138
- package/eslint.config.js +0 -18
- package/frontend-base.d.ts +0 -8
- package/jest.config.js +0 -7
- package/openedx-frontend-base.tgz +0 -0
- package/runtime/react/hooks.ts +0 -106
- package/test-site/app.d.ts +0 -15
- package/test-site/dist/176.436443549ebb858db483.js +0 -2
- package/test-site/dist/176.436443549ebb858db483.js.map +0 -1
- package/test-site/dist/362.536eff787d2380fe246c.js +0 -2
- package/test-site/dist/362.536eff787d2380fe246c.js.map +0 -1
- package/test-site/dist/653.486966b108d224551296.js +0 -2
- package/test-site/dist/653.486966b108d224551296.js.map +0 -1
- package/test-site/dist/74e025d3fe9a7b7f8503054e2563b353.jpg +0 -0
- package/test-site/dist/806.323cf6496ad0a7fe73a7.js +0 -3
- package/test-site/dist/806.323cf6496ad0a7fe73a7.js.LICENSE.txt +0 -106
- package/test-site/dist/806.323cf6496ad0a7fe73a7.js.map +0 -1
- package/test-site/dist/95ec738c0b7faac5b5c9126794446bbd.svg +0 -4
- package/test-site/dist/app.612058b36c74787759ac.css +0 -61
- package/test-site/dist/app.612058b36c74787759ac.css.map +0 -1
- package/test-site/dist/app.612058b36c74787759ac.js +0 -2
- package/test-site/dist/app.612058b36c74787759ac.js.map +0 -1
- package/test-site/dist/cb28cdb1468c915e27e5cec9af64f22f.svg +0 -1
- package/test-site/dist/index.html +0 -1
- package/test-site/dist/report.html +0 -39
- package/test-site/dist/runtime.c7aeaf7b967496cb076f.js +0 -2
- package/test-site/dist/runtime.c7aeaf7b967496cb076f.js.map +0 -1
- package/test-site/eslint.config.js +0 -12
- package/test-site/package-lock.json +0 -19226
- package/test-site/package.json +0 -29
- package/test-site/public/index.html +0 -10
- package/test-site/site.config.build.tsx +0 -27
- package/test-site/site.config.dev.tsx +0 -27
- package/test-site/src/authenticated-page/AuthenticatedPage.tsx +0 -18
- package/test-site/src/authenticated-page/i18n/index.ts +0 -27
- package/test-site/src/authenticated-page/index.tsx +0 -28
- package/test-site/src/example-page/ExamplePage.tsx +0 -79
- package/test-site/src/example-page/Image.tsx +0 -11
- package/test-site/src/example-page/ParagonPreview.jsx +0 -66
- package/test-site/src/example-page/apple.jpg +0 -0
- package/test-site/src/example-page/apple.svg +0 -1
- package/test-site/src/example-page/index.ts +0 -16
- package/test-site/src/i18n/README.md +0 -3
- package/test-site/src/i18n/messages/frontend-app-sample/ar.json +0 -4
- package/test-site/src/i18n/messages/frontend-app-sample/eo.json +0 -1
- package/test-site/src/i18n/messages/frontend-app-sample/es_419.json +0 -4
- package/test-site/src/i18n/messages/frontend-component-emptylangs/ar.json +0 -1
- package/test-site/src/i18n/messages/frontend-component-singlelang/ar.json +0 -3
- package/test-site/src/iframe-widget/IframeWidget.tsx +0 -14
- package/test-site/src/iframe-widget/index.ts +0 -16
- package/test-site/src/index.tsx +0 -3
- package/test-site/src/messages.js +0 -11
- package/test-site/src/site.scss +0 -11
- package/test-site/tsconfig.json +0 -14
- package/tools/babel/babel.config.js +0 -27
- package/tools/babel.config.js +0 -3
- package/tools/cli/README.md +0 -29
- package/tools/cli/commands/pack.ts +0 -9
- package/tools/cli/commands/release.ts +0 -27
- package/tools/cli/commands/serve.ts +0 -43
- package/tools/cli/intl-imports.ts +0 -274
- package/tools/cli/openedx.ts +0 -101
- package/tools/cli/transifex-utils.ts +0 -75
- package/tools/cli/utils/ensureConfigFilenameOption.ts +0 -40
- package/tools/cli/utils/formatter.ts +0 -10
- package/tools/cli/utils/getResolvedConfigPath.ts +0 -23
- package/tools/cli/utils/prettyPrintTitle.ts +0 -15
- package/tools/cli/utils/printUsage.ts +0 -53
- package/tools/config-helpers/createConfig.ts +0 -8
- package/tools/config-helpers/createLintConfig.ts +0 -14
- package/tools/config-helpers/getBaseConfig.ts +0 -11
- package/tools/defaultConfigPaths.ts +0 -30
- package/tools/dist/cli/commands/pack.js +0 -14
- package/tools/dist/cli/commands/release.js +0 -28
- package/tools/dist/webpack/plugins/paragon-webpack-plugin/ParagonWebpackPlugin.js +0 -108
- package/tools/dist/webpack/plugins/paragon-webpack-plugin/index.js +0 -7
- package/tools/dist/webpack/plugins/paragon-webpack-plugin/utils/assetUtils.js +0 -64
- package/tools/dist/webpack/plugins/paragon-webpack-plugin/utils/htmlUtils.js +0 -53
- package/tools/dist/webpack/plugins/paragon-webpack-plugin/utils/index.js +0 -9
- package/tools/dist/webpack/plugins/paragon-webpack-plugin/utils/paragonStylesheetUtils.js +0 -114
- package/tools/dist/webpack/plugins/paragon-webpack-plugin/utils/scriptUtils.js +0 -146
- package/tools/dist/webpack/plugins/paragon-webpack-plugin/utils/stylesheetUtils.js +0 -126
- package/tools/dist/webpack/plugins/paragon-webpack-plugin/utils/tagUtils.js +0 -57
- package/tools/dist/webpack/types.js +0 -2
- package/tools/dist/webpack/utils/paragonUtils.js +0 -138
- package/tools/eslint/base.eslint.config.js +0 -124
- package/tools/eslint/modules.d.ts +0 -5
- package/tools/eslint.config.js +0 -15
- package/tools/index.ts +0 -3
- package/tools/jest/jest.config.js +0 -30
- package/tools/jest.config.js +0 -19
- package/tools/tsconfig.json +0 -24
- package/tools/types.ts +0 -21
- package/tools/typescript/tsconfig.json +0 -32
- package/tools/webpack/common-config/README.md +0 -15
- package/tools/webpack/common-config/all/getCodeRules.ts +0 -51
- package/tools/webpack/common-config/all/getFileLoaderRules.ts +0 -23
- package/tools/webpack/common-config/all/getIgnoreWarnings.ts +0 -13
- package/tools/webpack/common-config/all/getImageMinimizer.ts +0 -26
- package/tools/webpack/common-config/all/getStylesheetRule.ts +0 -111
- package/tools/webpack/common-config/dev/getDevServer.ts +0 -35
- package/tools/webpack/common-config/index.ts +0 -6
- package/tools/webpack/common-config/site/getHtmlWebpackPlugin.ts +0 -11
- package/tools/webpack/modules.d.ts +0 -6
- package/tools/webpack/plugins/html-webpack-new-relic-plugin/HtmlWebpackNewRelicPlugin.ts +0 -102
- package/tools/webpack/plugins/html-webpack-new-relic-plugin/LICENSE +0 -21
- package/tools/webpack/plugins/html-webpack-new-relic-plugin/README.md +0 -7
- package/tools/webpack/plugins/html-webpack-new-relic-plugin/index.js +0 -3
- package/tools/webpack/plugins/html-webpack-new-relic-plugin/test/fixtures/entry.js +0 -1
- package/tools/webpack/plugins/paragon-webpack-plugin/ParagonWebpackPlugin.ts +0 -134
- package/tools/webpack/plugins/paragon-webpack-plugin/index.ts +0 -3
- package/tools/webpack/plugins/paragon-webpack-plugin/utils/assetUtils.ts +0 -71
- package/tools/webpack/plugins/paragon-webpack-plugin/utils/htmlUtils.ts +0 -72
- package/tools/webpack/plugins/paragon-webpack-plugin/utils/index.ts +0 -6
- package/tools/webpack/plugins/paragon-webpack-plugin/utils/paragonStylesheetUtils.ts +0 -131
- package/tools/webpack/plugins/paragon-webpack-plugin/utils/scriptUtils.ts +0 -144
- package/tools/webpack/plugins/paragon-webpack-plugin/utils/stylesheetUtils.ts +0 -106
- package/tools/webpack/plugins/paragon-webpack-plugin/utils/tagUtils.ts +0 -54
- package/tools/webpack/types.ts +0 -69
- package/tools/webpack/utils/getLocalAliases.ts +0 -65
- package/tools/webpack/utils/getPublicPath.ts +0 -3
- package/tools/webpack/utils/getResolvedSiteConfigPath.ts +0 -28
- package/tools/webpack/utils/paragonUtils.ts +0 -152
- package/tools/webpack/webpack.config.build.ts +0 -93
- package/tools/webpack/webpack.config.dev.shell.ts +0 -122
- package/tools/webpack/webpack.config.dev.ts +0 -90
- package/tsconfig.json +0 -23
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { renderHook } from '@testing-library/react';
|
|
2
|
+
import { sendTrackEvent } from '../../../analytics';
|
|
3
|
+
import useTrackColorSchemeChoice from './useTrackColorSchemeChoice';
|
|
4
|
+
|
|
5
|
+
jest.mock('../../../analytics');
|
|
6
|
+
|
|
7
|
+
const mockAddEventListener = jest.fn();
|
|
8
|
+
const mockRemoveEventListener = jest.fn();
|
|
9
|
+
let matchesMock;
|
|
10
|
+
|
|
11
|
+
Object.defineProperty(window, 'matchMedia', {
|
|
12
|
+
value: jest.fn(() => ({
|
|
13
|
+
addEventListener: mockAddEventListener,
|
|
14
|
+
removeEventListener: mockRemoveEventListener,
|
|
15
|
+
matches: matchesMock,
|
|
16
|
+
})),
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
describe('useTrackColorSchemeChoice hook', () => {
|
|
20
|
+
afterEach(() => {
|
|
21
|
+
mockAddEventListener.mockClear();
|
|
22
|
+
mockRemoveEventListener.mockClear();
|
|
23
|
+
jest.mocked(sendTrackEvent).mockClear();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('sends dark preferred color schema event if query matches', async () => {
|
|
27
|
+
matchesMock = true;
|
|
28
|
+
renderHook(() => useTrackColorSchemeChoice());
|
|
29
|
+
|
|
30
|
+
expect(sendTrackEvent).toHaveBeenCalledTimes(1);
|
|
31
|
+
expect(sendTrackEvent).toHaveBeenCalledWith(
|
|
32
|
+
'openedx.ui.frontend-base.prefers-color-scheme.selected',
|
|
33
|
+
{ preferredColorScheme: 'dark' },
|
|
34
|
+
);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('sends light preferred color schema event if query does not match', async () => {
|
|
38
|
+
matchesMock = false;
|
|
39
|
+
renderHook(() => useTrackColorSchemeChoice());
|
|
40
|
+
|
|
41
|
+
expect(sendTrackEvent).toHaveBeenCalledTimes(1);
|
|
42
|
+
expect(sendTrackEvent).toHaveBeenCalledWith(
|
|
43
|
+
'openedx.ui.frontend-base.prefers-color-scheme.selected',
|
|
44
|
+
{ preferredColorScheme: 'light' },
|
|
45
|
+
);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('adds change event listener to matchMedia query', async () => {
|
|
49
|
+
renderHook(() => useTrackColorSchemeChoice());
|
|
50
|
+
|
|
51
|
+
expect(mockAddEventListener).toHaveBeenCalledTimes(1);
|
|
52
|
+
expect(mockAddEventListener).toHaveBeenCalledWith('change', expect.any(Function));
|
|
53
|
+
});
|
|
54
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
|
+
import { sendTrackEvent } from '../../../analytics';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A React hook that tracks user's preferred color scheme (light or dark) and sends respective
|
|
6
|
+
* event to the tracking service.
|
|
7
|
+
*
|
|
8
|
+
* @memberof module:React
|
|
9
|
+
*/
|
|
10
|
+
const useTrackColorSchemeChoice = () => {
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
const trackColorSchemeChoice = ({ matches }) => {
|
|
13
|
+
const preferredColorScheme = matches ? 'dark' : 'light';
|
|
14
|
+
sendTrackEvent('openedx.ui.frontend-base.prefers-color-scheme.selected', { preferredColorScheme });
|
|
15
|
+
};
|
|
16
|
+
const colorSchemeQuery = window.matchMedia?.('(prefers-color-scheme: dark)');
|
|
17
|
+
if (colorSchemeQuery) {
|
|
18
|
+
// send user's initial choice
|
|
19
|
+
trackColorSchemeChoice(colorSchemeQuery);
|
|
20
|
+
colorSchemeQuery.addEventListener('change', trackColorSchemeChoice);
|
|
21
|
+
}
|
|
22
|
+
return () => {
|
|
23
|
+
if (colorSchemeQuery) {
|
|
24
|
+
colorSchemeQuery.removeEventListener('change', trackColorSchemeChoice);
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
}, []);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export default useTrackColorSchemeChoice;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Iterates through each given `<link>` element and removes it from the DOM.
|
|
3
|
+
* @param {HTMLLinkElement[]} existingLinks
|
|
4
|
+
*/
|
|
5
|
+
export const removeExistingLinks = (existingLinks) => {
|
|
6
|
+
existingLinks.forEach((link) => {
|
|
7
|
+
link.remove();
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const isEmptyObject = (obj) => !obj || Object.keys(obj).length === 0;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { getActiveRoles } from '../../config';
|
|
3
|
+
import { ACTIVE_ROLES_CHANGED } from '../../constants';
|
|
4
|
+
import useSiteEvent from './useSiteEvent';
|
|
5
|
+
|
|
6
|
+
const useActiveRoles = () => {
|
|
7
|
+
const [roles, setRoles] = useState<string[]>(getActiveRoles());
|
|
8
|
+
useSiteEvent(ACTIVE_ROLES_CHANGED, () => {
|
|
9
|
+
setRoles(getActiveRoles());
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
return roles;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export default useActiveRoles;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { useCallback, useEffect } from 'react';
|
|
2
|
+
import { useMatches } from 'react-router';
|
|
3
|
+
import { setActiveRouteRoles } from '../../config';
|
|
4
|
+
import { isRoleRouteObject } from '../../routing';
|
|
5
|
+
|
|
6
|
+
const useActiveRouteRoleWatcher = () => {
|
|
7
|
+
const matches = useMatches();
|
|
8
|
+
|
|
9
|
+
// We create this callback so we can use it right away to populate the default state value.
|
|
10
|
+
const findActiveRouteRoles = useCallback(() => {
|
|
11
|
+
// Starts with the widget roles and adds the others in.
|
|
12
|
+
const roles: string[] = [];
|
|
13
|
+
|
|
14
|
+
// Route roles
|
|
15
|
+
for (const match of matches) {
|
|
16
|
+
if (isRoleRouteObject(match)) {
|
|
17
|
+
if (!roles.includes(match.handle.role)) {
|
|
18
|
+
roles.push(match.handle.role);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return roles;
|
|
24
|
+
}, [matches]);
|
|
25
|
+
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
setActiveRouteRoles(findActiveRouteRoles());
|
|
28
|
+
}, [matches, findActiveRouteRoles]);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export default useActiveRouteRoleWatcher;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { act, renderHook } from '@testing-library/react';
|
|
2
|
+
import { setAuthenticatedUser } from '../../auth';
|
|
3
|
+
import { initializeMockApp } from '../../testing';
|
|
4
|
+
import SiteProvider from '../SiteProvider';
|
|
5
|
+
import useAuthenticatedUser from './useAuthenticatedUser';
|
|
6
|
+
|
|
7
|
+
describe('useAuthenticatedUser', () => {
|
|
8
|
+
it('returns null when the user is anonymous', () => {
|
|
9
|
+
const { result } = renderHook(() => useAuthenticatedUser());
|
|
10
|
+
expect(result.current).toBeNull();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
describe('with a user', () => {
|
|
14
|
+
const user = {
|
|
15
|
+
administrator: true,
|
|
16
|
+
email: 'admin@example.com',
|
|
17
|
+
name: 'Admin',
|
|
18
|
+
roles: ['admin'],
|
|
19
|
+
userId: 1,
|
|
20
|
+
username: 'admin-user',
|
|
21
|
+
avatar: 'http://localhost/admin.png',
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
beforeEach(() => {
|
|
25
|
+
initializeMockApp({
|
|
26
|
+
authenticatedUser: user,
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
afterEach(() => {
|
|
31
|
+
act(() => {
|
|
32
|
+
setAuthenticatedUser(null);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('returns a User when the user exists', () => {
|
|
37
|
+
const { result } = renderHook(() => useAuthenticatedUser(), { wrapper: SiteProvider });
|
|
38
|
+
expect(result.current).toBe(user);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { renderHook } from '@testing-library/react';
|
|
2
|
+
import siteConfig from 'site.config';
|
|
3
|
+
import { EnvironmentTypes } from '../../../types';
|
|
4
|
+
import useSiteConfig from './useSiteConfig';
|
|
5
|
+
|
|
6
|
+
describe('useSiteConfig', () => {
|
|
7
|
+
it('returns the site config', () => {
|
|
8
|
+
const { result } = renderHook(() => useSiteConfig());
|
|
9
|
+
expect(result.current).toHaveProperty('apps', siteConfig.apps);
|
|
10
|
+
expect(result.current).toHaveProperty('environment', EnvironmentTypes.TEST);
|
|
11
|
+
expect(result.current).toHaveProperty('baseUrl', 'http://localhost:8080');
|
|
12
|
+
});
|
|
13
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
|
+
import { subscribe, unsubscribe } from '../../subscriptions';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A React hook that allows functional components to subscribe to application events. This should
|
|
6
|
+
* be used sparingly - for the most part, Context should be used higher-up in the application to
|
|
7
|
+
* provide necessary data to a given component, rather than utilizing a non-React-like Pub/Sub
|
|
8
|
+
* mechanism.
|
|
9
|
+
*
|
|
10
|
+
* @memberof module:React
|
|
11
|
+
* @param {string} type
|
|
12
|
+
* @param {function} callback
|
|
13
|
+
*/
|
|
14
|
+
const useSiteEvent = (type, callback) => {
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
subscribe(type, callback);
|
|
17
|
+
|
|
18
|
+
return () => {
|
|
19
|
+
unsubscribe(type, callback);
|
|
20
|
+
};
|
|
21
|
+
}, [callback, type]);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export default useSiteEvent;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SET_THEME_VARIANT,
|
|
3
|
+
SET_IS_THEME_LOADED,
|
|
4
|
+
} from './constants';
|
|
5
|
+
|
|
6
|
+
export function themeReducer(state, action) {
|
|
7
|
+
switch (action.type) {
|
|
8
|
+
case SET_THEME_VARIANT: {
|
|
9
|
+
const requestedThemeVariant = action.payload;
|
|
10
|
+
return {
|
|
11
|
+
...state,
|
|
12
|
+
themeVariant: requestedThemeVariant,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
case SET_IS_THEME_LOADED: {
|
|
16
|
+
const requestedIsThemeLoaded = action.payload;
|
|
17
|
+
return {
|
|
18
|
+
...state,
|
|
19
|
+
isThemeLoaded: requestedIsThemeLoaded,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
default:
|
|
23
|
+
return state;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const setThemeVariant = (payload) => ({
|
|
28
|
+
type: SET_THEME_VARIANT,
|
|
29
|
+
payload,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const setThemeLoaded = (payload) => ({
|
|
33
|
+
type: SET_IS_THEME_LOADED,
|
|
34
|
+
payload,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
export const themeActions = {
|
|
38
|
+
setThemeVariant,
|
|
39
|
+
setThemeLoaded,
|
|
40
|
+
};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { GoogleAnalyticsLoader } from './index';
|
|
2
|
+
|
|
3
|
+
const googleAnalyticsId = 'test-key';
|
|
4
|
+
|
|
5
|
+
describe('GoogleAnalytics', () => {
|
|
6
|
+
let body;
|
|
7
|
+
let gaScriptSrc;
|
|
8
|
+
let gaScriptGtag;
|
|
9
|
+
let data;
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
// @ts-expect-error We're adding it, this is fine.
|
|
13
|
+
window.googleAnalytics = [];
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
function loadGoogleAnalytics(scriptData) {
|
|
17
|
+
const script = new GoogleAnalyticsLoader(scriptData);
|
|
18
|
+
script.loadScript();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
describe('with valid GOOGLE_ANALYTICS_4_ID', () => {
|
|
22
|
+
beforeEach(() => {
|
|
23
|
+
document.body.innerHTML = '<script id="stub" />';
|
|
24
|
+
data = {
|
|
25
|
+
config: {
|
|
26
|
+
GOOGLE_ANALYTICS_4_ID: googleAnalyticsId,
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
loadGoogleAnalytics(data);
|
|
30
|
+
expect(global.googleAnalytics.invoked).toBe(true);
|
|
31
|
+
body = document.body.innerHTML;
|
|
32
|
+
gaScriptSrc = `https://www.googletagmanager.com/gtag/js?id=${googleAnalyticsId}`;
|
|
33
|
+
gaScriptGtag = `gtag('config', '${googleAnalyticsId}');`;
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should initialize google analytics', () => {
|
|
37
|
+
expect(body).toMatch(gaScriptSrc);
|
|
38
|
+
expect(body).toMatch(gaScriptGtag);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('should not invoke snippet twice', () => {
|
|
42
|
+
loadGoogleAnalytics(data);
|
|
43
|
+
|
|
44
|
+
expect(global.googleAnalytics.invoked).toBe(true);
|
|
45
|
+
|
|
46
|
+
expect(body).toMatch(gaScriptSrc);
|
|
47
|
+
expect(body).toMatch(gaScriptGtag);
|
|
48
|
+
|
|
49
|
+
let count = (body.match(new RegExp(gaScriptSrc.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g')) || []).length;
|
|
50
|
+
expect(count).toBe(1);
|
|
51
|
+
|
|
52
|
+
count = (body.match(new RegExp(gaScriptGtag.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g')) || []).length;
|
|
53
|
+
expect(count).toBe(1);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe('with invalid GOOGLE_ANALYTICS_ID', () => {
|
|
58
|
+
beforeEach(() => {
|
|
59
|
+
document.body.innerHTML = '<script id="stub" />';
|
|
60
|
+
data = {
|
|
61
|
+
config: {
|
|
62
|
+
GOOGLE_ANALYTICS_4_ID: '',
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
loadGoogleAnalytics(data);
|
|
66
|
+
body = document.body.innerHTML;
|
|
67
|
+
gaScriptSrc = 'https://www.googletagmanager.com/gtag/js?id=';
|
|
68
|
+
gaScriptGtag = "gtag('config', '');";
|
|
69
|
+
expect(global.googleAnalytics.invoked).toBeFalsy();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('should not initialize google analytics', () => {
|
|
73
|
+
expect(body).not.toMatch(gaScriptSrc);
|
|
74
|
+
expect(body).not.toMatch(gaScriptGtag);
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
});
|
package/runtime/setupTest.js
CHANGED
|
@@ -12,38 +12,3 @@ jest.mock('universal-cookie', () => {
|
|
|
12
12
|
});
|
|
13
13
|
|
|
14
14
|
mergeSiteConfig(siteConfig);
|
|
15
|
-
|
|
16
|
-
global.PARAGON_THEME = {
|
|
17
|
-
paragon: {
|
|
18
|
-
version: '1.0.0',
|
|
19
|
-
themeUrls: {
|
|
20
|
-
core: {
|
|
21
|
-
fileName: 'core.min.css',
|
|
22
|
-
},
|
|
23
|
-
defaults: {
|
|
24
|
-
light: 'light',
|
|
25
|
-
},
|
|
26
|
-
variants: {
|
|
27
|
-
light: {
|
|
28
|
-
fileName: 'light.min.css',
|
|
29
|
-
},
|
|
30
|
-
},
|
|
31
|
-
},
|
|
32
|
-
},
|
|
33
|
-
brand: {
|
|
34
|
-
version: '1.0.0',
|
|
35
|
-
themeUrls: {
|
|
36
|
-
core: {
|
|
37
|
-
fileName: 'core.min.css',
|
|
38
|
-
},
|
|
39
|
-
defaults: {
|
|
40
|
-
light: 'light',
|
|
41
|
-
},
|
|
42
|
-
variants: {
|
|
43
|
-
light: {
|
|
44
|
-
fileName: 'light.min.css',
|
|
45
|
-
},
|
|
46
|
-
},
|
|
47
|
-
},
|
|
48
|
-
},
|
|
49
|
-
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { EnvironmentTypes, SiteConfig } from '../types';
|
|
2
|
+
|
|
3
|
+
const siteConfig: SiteConfig = {
|
|
4
|
+
siteId: 'runtime',
|
|
5
|
+
siteName: 'edX',
|
|
6
|
+
baseUrl: 'http://localhost:8080',
|
|
7
|
+
lmsBaseUrl: 'http://localhost:18000',
|
|
8
|
+
loginUrl: 'http://localhost:18000/login',
|
|
9
|
+
logoutUrl: 'http://localhost:18000/logout',
|
|
10
|
+
|
|
11
|
+
environment: EnvironmentTypes.TEST,
|
|
12
|
+
apps: [{
|
|
13
|
+
appId: 'test-app',
|
|
14
|
+
routes: [{
|
|
15
|
+
path: '/app1',
|
|
16
|
+
element: (
|
|
17
|
+
<div>Test App 1</div>
|
|
18
|
+
),
|
|
19
|
+
handle: {
|
|
20
|
+
role: 'test-app-1'
|
|
21
|
+
}
|
|
22
|
+
}]
|
|
23
|
+
}],
|
|
24
|
+
accessTokenCookieName: 'edx-jwt-cookie-header-payload',
|
|
25
|
+
csrfTokenApiPath: '/csrf/api/v1/token',
|
|
26
|
+
languagePreferenceCookieName: 'openedx-language-preference',
|
|
27
|
+
refreshAccessTokenApiPath: '/login_refresh',
|
|
28
|
+
userInfoCookieName: 'edx-user-info',
|
|
29
|
+
ignoredErrorRegex: null,
|
|
30
|
+
segmentKey: 'segment_whoa',
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export default siteConfig;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { render } from '@testing-library/react';
|
|
2
|
+
import { MemoryRouter } from 'react-router-dom';
|
|
3
|
+
import { useContext } from 'react';
|
|
4
|
+
import { useLayoutForSlotId } from './layout/hooks';
|
|
5
|
+
import Slot from './Slot';
|
|
6
|
+
import SlotContext from './SlotContext';
|
|
7
|
+
|
|
8
|
+
jest.mock('./layout/hooks');
|
|
9
|
+
|
|
10
|
+
describe('Slot component', () => {
|
|
11
|
+
it('renders with default layout', () => {
|
|
12
|
+
(useLayoutForSlotId as jest.Mock).mockReturnValue(null);
|
|
13
|
+
const { container } = render(<MemoryRouter><Slot id="test-slot.ui" /></MemoryRouter>);
|
|
14
|
+
expect(container).toBeEmptyDOMElement();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('successfully passes the slot ID through a SlotContext', () => {
|
|
18
|
+
function IdPrintingLayout() {
|
|
19
|
+
const { id } = useContext(SlotContext);
|
|
20
|
+
return <div>Slot ID: {id}</div>;
|
|
21
|
+
}
|
|
22
|
+
(useLayoutForSlotId as jest.Mock).mockReturnValue(IdPrintingLayout);
|
|
23
|
+
const { getByText } = render(<Slot id="test-slot.ui" />);
|
|
24
|
+
expect(getByText('Slot ID: test-slot.ui')).toBeInTheDocument();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('renders with a component override layout', () => {
|
|
28
|
+
const CustomLayoutComponent = () => <div>Custom Layout Component</div>;
|
|
29
|
+
(useLayoutForSlotId as jest.Mock).mockReturnValue(CustomLayoutComponent);
|
|
30
|
+
const { getByText } = render(<Slot id="test-slot.ui" />);
|
|
31
|
+
expect(getByText('Custom Layout Component')).toBeInTheDocument();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('renders with an element override layout', () => {
|
|
35
|
+
const CustomLayoutElement = <div>Custom Layout Element</div>;
|
|
36
|
+
(useLayoutForSlotId as jest.Mock).mockReturnValue(null);
|
|
37
|
+
const { getByText } = render(<Slot id="test-slot.ui" layout={CustomLayoutElement} />);
|
|
38
|
+
expect(getByText('Custom Layout Element')).toBeInTheDocument();
|
|
39
|
+
});
|
|
40
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { render } from '@testing-library/react';
|
|
2
|
+
import { useWidgets } from '../widget/hooks';
|
|
3
|
+
import DefaultSlotLayout from './DefaultSlotLayout';
|
|
4
|
+
|
|
5
|
+
// Mock the useWidgets hook
|
|
6
|
+
jest.mock('../widget/hooks', () => ({
|
|
7
|
+
useWidgets: jest.fn(),
|
|
8
|
+
}));
|
|
9
|
+
|
|
10
|
+
describe('DefaultSlotLayout', () => {
|
|
11
|
+
it('renders widgets returned by useWidgets hook', () => {
|
|
12
|
+
const mockWidgets = [
|
|
13
|
+
<div key="widget1">Widget One</div>,
|
|
14
|
+
<div key="widget2">Widget Two</div>,
|
|
15
|
+
];
|
|
16
|
+
(useWidgets as jest.Mock).mockReturnValue(mockWidgets);
|
|
17
|
+
|
|
18
|
+
const { getByText } = render(<DefaultSlotLayout />);
|
|
19
|
+
|
|
20
|
+
expect(getByText('Widget One')).toBeInTheDocument();
|
|
21
|
+
expect(getByText('Widget Two')).toBeInTheDocument();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('renders empty when no widgets are returned', () => {
|
|
25
|
+
(useWidgets as jest.Mock).mockReturnValue([]);
|
|
26
|
+
|
|
27
|
+
const { container } = render(<DefaultSlotLayout />);
|
|
28
|
+
|
|
29
|
+
expect(container).toBeEmptyDOMElement();
|
|
30
|
+
});
|
|
31
|
+
});
|