@openedx/frontend-base 1.0.0-alpha.0 → 1.0.0-alpha.2
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/types.js +0 -2
- package/config/webpack/plugins/html-webpack-new-relic-plugin/test/HtmlWebpackNewRelicPlugin.test.js +66 -0
- package/package.json +8 -1
- 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/i18n/lib.test.js +230 -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/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/hooks.test.jsx +104 -0
- package/runtime/routing/utils.test.ts +7 -0
- package/runtime/scripts/GoogleAnalyticsLoader.test.ts +77 -0
- 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/testing/initializeMockApp.test.ts +66 -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.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/router/createRouter.test.tsx +50 -0
- package/shell/router/getAppRoutes.test.tsx +59 -0
- package/shell/site.config.dev.tsx +3 -3
- package/shell/site.config.test.tsx +16 -0
- 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/types.js +0 -2
- package/tools/dist/webpack/plugins/html-webpack-new-relic-plugin/test/HtmlWebpackNewRelicPlugin.test.js +66 -0
- package/types.ts +1 -1
- 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/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/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
package/runtime/slots/types.ts
CHANGED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { getSiteConfig } from '../config';
|
|
2
|
+
import { SlotOperation } from './types';
|
|
3
|
+
import { getSlotOperations } from './utils';
|
|
4
|
+
import { WidgetOperationTypes } from '.';
|
|
5
|
+
|
|
6
|
+
jest.mock('../config');
|
|
7
|
+
|
|
8
|
+
describe('getSlotOperations', () => {
|
|
9
|
+
it('should return an empty array if no apps are configured', () => {
|
|
10
|
+
(getSiteConfig as jest.Mock).mockReturnValue({ apps: [] });
|
|
11
|
+
const result = getSlotOperations('test-slot.ui');
|
|
12
|
+
expect(result).toEqual([]);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('should return an empty array if no slots are present in apps', () => {
|
|
16
|
+
(getSiteConfig as jest.Mock).mockReturnValue({ apps: [{ slots: [] }] });
|
|
17
|
+
const result = getSlotOperations('test-slot.ui');
|
|
18
|
+
expect(result).toEqual([]);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('should return an empty array if no matching slotId is found', () => {
|
|
22
|
+
const mockSlots: SlotOperation[] = [{ slotId: 'other-slot.ui', op: WidgetOperationTypes.APPEND, id: 'widget1', element: '' }];
|
|
23
|
+
(getSiteConfig as jest.Mock).mockReturnValue({ apps: [{ slots: mockSlots }] });
|
|
24
|
+
const result = getSlotOperations('test-slot.ui');
|
|
25
|
+
expect(result).toEqual([]);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should return the correct slot operations for a given slotId', () => {
|
|
29
|
+
const mockSlots: SlotOperation[] = [
|
|
30
|
+
{ slotId: 'test-slot.ui', op: WidgetOperationTypes.APPEND, id: 'widget1', element: '' },
|
|
31
|
+
{ slotId: 'test-slot.ui', op: WidgetOperationTypes.APPEND, id: 'widget2', element: '' },
|
|
32
|
+
{ slotId: 'other-slot.ui', op: WidgetOperationTypes.APPEND, id: 'widget3', element: '' },
|
|
33
|
+
];
|
|
34
|
+
(getSiteConfig as jest.Mock).mockReturnValue({ apps: [{ slots: mockSlots }] });
|
|
35
|
+
const result = getSlotOperations('test-slot.ui');
|
|
36
|
+
expect(result).toEqual([
|
|
37
|
+
{ slotId: 'test-slot.ui', op: WidgetOperationTypes.APPEND, id: 'widget1', element: '' },
|
|
38
|
+
{ slotId: 'test-slot.ui', op: WidgetOperationTypes.APPEND, id: 'widget2', element: '' },
|
|
39
|
+
]);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('should handle multiple apps with slots correctly', () => {
|
|
43
|
+
(getSiteConfig as jest.Mock).mockReturnValue({
|
|
44
|
+
apps: [
|
|
45
|
+
{
|
|
46
|
+
slots: [
|
|
47
|
+
{ slotId: 'test-slot.ui', op: WidgetOperationTypes.APPEND, id: 'widget1', element: '' },
|
|
48
|
+
]
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
slots: [
|
|
52
|
+
{ slotId: 'test-slot.ui', op: WidgetOperationTypes.APPEND, id: 'widget2', element: '' },
|
|
53
|
+
{ slotId: 'other-slot.ui', op: WidgetOperationTypes.APPEND, id: 'widget3', element: '' },
|
|
54
|
+
]
|
|
55
|
+
}
|
|
56
|
+
]
|
|
57
|
+
});
|
|
58
|
+
const result = getSlotOperations('test-slot.ui');
|
|
59
|
+
expect(result).toEqual([
|
|
60
|
+
{ slotId: 'test-slot.ui', op: WidgetOperationTypes.APPEND, id: 'widget1', element: '' },
|
|
61
|
+
{ slotId: 'test-slot.ui', op: WidgetOperationTypes.APPEND, id: 'widget2', element: '' },
|
|
62
|
+
]);
|
|
63
|
+
});
|
|
64
|
+
});
|
package/runtime/slots/utils.ts
CHANGED
|
@@ -35,18 +35,37 @@ export function isSlotOperationConditionSatisfied(operation: SlotOperation) {
|
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
if (condition?.active !== undefined) {
|
|
39
|
-
let activeConditionRoleFound = false;
|
|
38
|
+
if (condition?.active !== undefined || condition?.inactive !== undefined) {
|
|
40
39
|
const activeRoles: string[] = getActiveRoles();
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
40
|
+
|
|
41
|
+
if (condition?.active !== undefined) {
|
|
42
|
+
let activeConditionRoleFound = false;
|
|
43
|
+
for (const conditionRole of condition.active) {
|
|
44
|
+
if (activeRoles.includes(conditionRole)) {
|
|
45
|
+
activeConditionRoleFound = true;
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// If we couldn't find an active role in our list, then we've failed this condition.
|
|
51
|
+
if (!activeConditionRoleFound) {
|
|
52
|
+
return false;
|
|
45
53
|
}
|
|
46
54
|
}
|
|
47
|
-
|
|
48
|
-
if (
|
|
49
|
-
|
|
55
|
+
|
|
56
|
+
if (condition?.inactive !== undefined) {
|
|
57
|
+
let inactiveConditionRoleFound = false;
|
|
58
|
+
for (const conditionRole of condition.inactive) {
|
|
59
|
+
if (activeRoles.includes(conditionRole)) {
|
|
60
|
+
inactiveConditionRoleFound = true;
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// If we find an active role from our inactive list, then we've failed this condition.
|
|
66
|
+
if (inactiveConditionRoleFound) {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
50
69
|
}
|
|
51
70
|
}
|
|
52
71
|
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getAnalyticsService,
|
|
3
|
+
MockAnalyticsService,
|
|
4
|
+
sendTrackEvent,
|
|
5
|
+
} from '../analytics';
|
|
6
|
+
import {
|
|
7
|
+
ensureAuthenticatedUser,
|
|
8
|
+
getAuthService,
|
|
9
|
+
getLoginRedirectUrl,
|
|
10
|
+
MockAuthService,
|
|
11
|
+
setAuthenticatedUser,
|
|
12
|
+
} from '../auth';
|
|
13
|
+
import {
|
|
14
|
+
getLoggingService,
|
|
15
|
+
logInfo,
|
|
16
|
+
MockLoggingService,
|
|
17
|
+
} from '../logging';
|
|
18
|
+
import initializeMockApp from './initializeMockApp';
|
|
19
|
+
|
|
20
|
+
describe('initializeMockApp', () => {
|
|
21
|
+
it('should create mock analytics, auth, and logging services, and a real i18n service', () => {
|
|
22
|
+
const {
|
|
23
|
+
analyticsService,
|
|
24
|
+
authService,
|
|
25
|
+
loggingService,
|
|
26
|
+
} = initializeMockApp();
|
|
27
|
+
|
|
28
|
+
expect(getAnalyticsService()).toBeInstanceOf(MockAnalyticsService);
|
|
29
|
+
expect(getAuthService()).toBeInstanceOf(MockAuthService);
|
|
30
|
+
expect(getLoggingService()).toBeInstanceOf(MockLoggingService);
|
|
31
|
+
|
|
32
|
+
const customAttributes = { custom: 'attribute' };
|
|
33
|
+
|
|
34
|
+
// Next, test a representative sample of functionality to prove mocking is working the way we hope.
|
|
35
|
+
|
|
36
|
+
// Analytics
|
|
37
|
+
sendTrackEvent('testEvent', customAttributes);
|
|
38
|
+
expect(analyticsService.sendTrackEvent).toHaveBeenCalledWith('testEvent', customAttributes);
|
|
39
|
+
// The mock analytics service calls checkIdentifyCalled when sendTrackEvent is called. Prove
|
|
40
|
+
// the jest.fn() works.
|
|
41
|
+
expect(analyticsService.checkIdentifyCalled).toHaveBeenCalledTimes(1);
|
|
42
|
+
|
|
43
|
+
// Auth
|
|
44
|
+
getLoginRedirectUrl('http://localhost/redirect/url');
|
|
45
|
+
setAuthenticatedUser({
|
|
46
|
+
userId: 'abc123',
|
|
47
|
+
username: 'Mock User',
|
|
48
|
+
roles: [],
|
|
49
|
+
administrator: false,
|
|
50
|
+
name: 'mock tester',
|
|
51
|
+
});
|
|
52
|
+
ensureAuthenticatedUser();
|
|
53
|
+
|
|
54
|
+
expect(authService.getLoginRedirectUrl).toHaveBeenCalledWith('http://localhost/redirect/url');
|
|
55
|
+
expect(authService.ensureAuthenticatedUser).toHaveBeenCalled();
|
|
56
|
+
// ensureAuthenticatedUser calls a few other things under the covers. Prove our mocking is
|
|
57
|
+
// working as expected across multiple levels.
|
|
58
|
+
expect(authService.fetchAuthenticatedUser).toHaveBeenCalled();
|
|
59
|
+
expect(authService.getAuthenticatedUser).toHaveBeenCalled();
|
|
60
|
+
expect(authService.redirectToLogin).not.toHaveBeenCalled();
|
|
61
|
+
|
|
62
|
+
// Logging
|
|
63
|
+
logInfo('logging info', customAttributes);
|
|
64
|
+
expect(loggingService.logInfo).toHaveBeenCalledWith('logging info', customAttributes);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { waitFor } from '@testing-library/react';
|
|
2
|
+
import {
|
|
3
|
+
camelCaseObject,
|
|
4
|
+
convertKeyNames,
|
|
5
|
+
getQueryParameters,
|
|
6
|
+
modifyObjectKeys,
|
|
7
|
+
snakeCaseObject,
|
|
8
|
+
} from '.';
|
|
9
|
+
|
|
10
|
+
describe('modifyObjectKeys', () => {
|
|
11
|
+
it('should use the provided modify function to change all keys in and object and its children', () => {
|
|
12
|
+
function meowKeys(key) {
|
|
13
|
+
return `${key}Meow`;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const result = modifyObjectKeys(
|
|
17
|
+
{
|
|
18
|
+
one: undefined,
|
|
19
|
+
two: null,
|
|
20
|
+
three: '',
|
|
21
|
+
four: 0,
|
|
22
|
+
five: NaN,
|
|
23
|
+
six: [1, 2, { seven: 'woof' }],
|
|
24
|
+
eight: { nine: { ten: 'bark' }, eleven: true },
|
|
25
|
+
},
|
|
26
|
+
meowKeys,
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
expect(result).toEqual({
|
|
30
|
+
oneMeow: undefined,
|
|
31
|
+
twoMeow: null,
|
|
32
|
+
threeMeow: '',
|
|
33
|
+
fourMeow: 0,
|
|
34
|
+
fiveMeow: NaN,
|
|
35
|
+
sixMeow: [1, 2, { sevenMeow: 'woof' }],
|
|
36
|
+
eightMeow: { nineMeow: { tenMeow: 'bark' }, elevenMeow: true },
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe('camelCaseObject', () => {
|
|
42
|
+
it('should make everything camelCase', () => {
|
|
43
|
+
const result = camelCaseObject({
|
|
44
|
+
what_now: 'brown cow',
|
|
45
|
+
but_who: { says_you_people: 'okay then', but_how: { will_we_even_know: 'the song is over' } },
|
|
46
|
+
'dot.dot.dot': 123,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
expect(result).toEqual({
|
|
50
|
+
whatNow: 'brown cow',
|
|
51
|
+
butWho: { saysYouPeople: 'okay then', butHow: { willWeEvenKnow: 'the song is over' } },
|
|
52
|
+
dotDotDot: 123,
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe('snakeCaseObject', () => {
|
|
58
|
+
it('should make everything snake_case', () => {
|
|
59
|
+
const result = snakeCaseObject({
|
|
60
|
+
whatNow: 'brown cow',
|
|
61
|
+
butWho: { saysYouPeople: 'okay then', butHow: { willWeEvenKnow: 'the song is over' } },
|
|
62
|
+
'dot.dot.dot': 123,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
expect(result).toEqual({
|
|
66
|
+
what_now: 'brown cow',
|
|
67
|
+
but_who: { says_you_people: 'okay then', but_how: { will_we_even_know: 'the song is over' } },
|
|
68
|
+
dot_dot_dot: 123,
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe('convertKeyNames', () => {
|
|
74
|
+
it('should replace the specified keynames', () => {
|
|
75
|
+
const result = convertKeyNames(
|
|
76
|
+
{
|
|
77
|
+
one: { two: { three: 'four' } },
|
|
78
|
+
five: 'six',
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
two: 'blue',
|
|
82
|
+
five: 'alive',
|
|
83
|
+
seven: 'heaven',
|
|
84
|
+
},
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
expect(result).toEqual({
|
|
88
|
+
one: { blue: { three: 'four' } },
|
|
89
|
+
alive: 'six',
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
describe('getQueryParameters', () => {
|
|
95
|
+
it('should use window location by default', () => {
|
|
96
|
+
expect(global.location.search).toEqual('');
|
|
97
|
+
expect(getQueryParameters()).toEqual({});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('should make an empty object with no query string', () => {
|
|
101
|
+
expect(getQueryParameters('')).toEqual({});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('should make an object with one key value pair', () => {
|
|
105
|
+
expect(getQueryParameters('?foo=bar')).toEqual({
|
|
106
|
+
foo: 'bar',
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('should make an object with one key value pair', () => {
|
|
111
|
+
expect(getQueryParameters('?foo=bar&baz=1')).toEqual({
|
|
112
|
+
foo: 'bar',
|
|
113
|
+
baz: '1',
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import '@testing-library/jest-dom';
|
|
2
|
+
import { render } from '@testing-library/react';
|
|
3
|
+
import Logo from './Logo';
|
|
4
|
+
|
|
5
|
+
describe('Logo component', () => {
|
|
6
|
+
it('renders the image with default URL when no props are provided', async () => {
|
|
7
|
+
const { getByRole, queryByRole } = render(<Logo />);
|
|
8
|
+
const image = getByRole('img');
|
|
9
|
+
expect(image).toHaveAttribute('src', 'https://edx-cdn.org/v3/default/logo.svg');
|
|
10
|
+
const link = queryByRole('link');
|
|
11
|
+
expect(link).toBeNull();
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('renders the image with provided imageUrl', () => {
|
|
15
|
+
const testUrl = 'https://example.com/test-logo.svg';
|
|
16
|
+
const { getByRole, queryByRole } = render(<Logo imageUrl={testUrl} />);
|
|
17
|
+
const image = getByRole('img');
|
|
18
|
+
expect(image).toHaveAttribute('src', testUrl);
|
|
19
|
+
const link = queryByRole('link');
|
|
20
|
+
expect(link).toBeNull();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('renders the image wrapped in a Hyperlink when destinationUrl is provided', () => {
|
|
24
|
+
const testDestinationUrl = 'https://example.com';
|
|
25
|
+
const { getByRole } = render(<Logo destinationUrl={testDestinationUrl} />);
|
|
26
|
+
const link = getByRole('link');
|
|
27
|
+
expect(link).toHaveAttribute('href', testDestinationUrl);
|
|
28
|
+
const image = getByRole('img');
|
|
29
|
+
expect(image).toBeInTheDocument();
|
|
30
|
+
expect(image).toHaveAttribute('src', 'https://edx-cdn.org/v3/default/logo.svg');
|
|
31
|
+
});
|
|
32
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default 'FileMock';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default 'SvgURL';
|
package/shell/app.ts
CHANGED
|
@@ -3,6 +3,14 @@ import { App } from '../types';
|
|
|
3
3
|
import { Footer } from './footer';
|
|
4
4
|
import { Header } from './header';
|
|
5
5
|
|
|
6
|
+
const inactive = [
|
|
7
|
+
'org.openedx.frontend.role.login',
|
|
8
|
+
'org.openedx.frontend.role.register',
|
|
9
|
+
'org.openedx.frontend.role.resetPassword',
|
|
10
|
+
'org.openedx.frontend.role.confirmPassword',
|
|
11
|
+
'org.openedx.frontend.role.welcome'
|
|
12
|
+
];
|
|
13
|
+
|
|
6
14
|
const app: App = {
|
|
7
15
|
appId: 'org.openedx.frontend.app.shell',
|
|
8
16
|
slots: [
|
|
@@ -11,12 +19,18 @@ const app: App = {
|
|
|
11
19
|
id: 'org.openedx.frontend.widget.header.main.v1',
|
|
12
20
|
op: WidgetOperationTypes.APPEND,
|
|
13
21
|
component: Header,
|
|
22
|
+
condition: {
|
|
23
|
+
inactive,
|
|
24
|
+
}
|
|
14
25
|
},
|
|
15
26
|
{
|
|
16
27
|
slotId: 'org.openedx.frontend.slot.footer.main.v1',
|
|
17
28
|
id: 'org.openedx.frontend.widget.footer.main.v1',
|
|
18
29
|
op: WidgetOperationTypes.APPEND,
|
|
19
30
|
component: Footer,
|
|
31
|
+
condition: {
|
|
32
|
+
inactive,
|
|
33
|
+
}
|
|
20
34
|
},
|
|
21
35
|
]
|
|
22
36
|
};
|
package/shell/dev/devHome/app.ts
CHANGED
|
@@ -6,10 +6,10 @@ const app: App = {
|
|
|
6
6
|
appId: 'org.openedx.frontend.app.dev.home',
|
|
7
7
|
routes: [{
|
|
8
8
|
path: '/',
|
|
9
|
-
id: 'dev.home',
|
|
9
|
+
id: 'org.openedx.frontend.route.dev.home',
|
|
10
10
|
Component: HomePage,
|
|
11
11
|
handle: {
|
|
12
|
-
role: '
|
|
12
|
+
role: 'org.openedx.frontend.role.devHome'
|
|
13
13
|
}
|
|
14
14
|
}],
|
|
15
15
|
messages,
|
|
@@ -43,13 +43,13 @@ function TakesPropsViaContext() {
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
const app: App = {
|
|
46
|
-
appId: 'org.openedx.frontend.app.
|
|
46
|
+
appId: 'org.openedx.frontend.app.slotShowcase',
|
|
47
47
|
routes: [{
|
|
48
|
-
id: 'org.openedx.frontend.route.
|
|
48
|
+
id: 'org.openedx.frontend.route.slotShowcase',
|
|
49
49
|
path: '/slots',
|
|
50
50
|
Component: SlotShowcasePage,
|
|
51
51
|
handle: {
|
|
52
|
-
role: 'slotShowcase',
|
|
52
|
+
role: 'org.openedx.frontend.role.slotShowcase',
|
|
53
53
|
}
|
|
54
54
|
}],
|
|
55
55
|
slots: [
|
|
@@ -314,7 +314,7 @@ const app: App = {
|
|
|
314
314
|
title: 'Courses (modified)',
|
|
315
315
|
},
|
|
316
316
|
condition: {
|
|
317
|
-
active: ['slotShowcase'],
|
|
317
|
+
active: ['org.openedx.frontend.role.slotShowcase'],
|
|
318
318
|
}
|
|
319
319
|
},
|
|
320
320
|
{
|
|
@@ -324,7 +324,7 @@ const app: App = {
|
|
|
324
324
|
relatedId: 'org.openedx.frontend.widget.slotShowcase.headerLink3',
|
|
325
325
|
element: (<LinkMenuItem label="Link After 3" url="#" variant="navLink" />),
|
|
326
326
|
condition: {
|
|
327
|
-
active: ['slotShowcase'],
|
|
327
|
+
active: ['org.openedx.frontend.role.slotShowcase'],
|
|
328
328
|
}
|
|
329
329
|
},
|
|
330
330
|
{
|
|
@@ -335,7 +335,7 @@ const app: App = {
|
|
|
335
335
|
<NavDropdownMenuSlot id="org.openedx.frontend.slot.header.primaryLinksDropdown.v1" label="Resources" />
|
|
336
336
|
),
|
|
337
337
|
condition: {
|
|
338
|
-
active: ['slotShowcase']
|
|
338
|
+
active: ['org.openedx.frontend.role.slotShowcase']
|
|
339
339
|
}
|
|
340
340
|
},
|
|
341
341
|
{
|
|
@@ -346,7 +346,7 @@ const app: App = {
|
|
|
346
346
|
<LinkMenuItem label="Resource 1" url="#" variant="dropdownItem" />
|
|
347
347
|
),
|
|
348
348
|
condition: {
|
|
349
|
-
active: ['slotShowcase'],
|
|
349
|
+
active: ['org.openedx.frontend.role.slotShowcase'],
|
|
350
350
|
}
|
|
351
351
|
},
|
|
352
352
|
{
|
|
@@ -355,7 +355,7 @@ const app: App = {
|
|
|
355
355
|
op: WidgetOperationTypes.APPEND,
|
|
356
356
|
element: (<LinkMenuItem label="Link 3" url="#" variant="navLink" />),
|
|
357
357
|
condition: {
|
|
358
|
-
active: ['slotShowcase'],
|
|
358
|
+
active: ['org.openedx.frontend.role.slotShowcase'],
|
|
359
359
|
}
|
|
360
360
|
},
|
|
361
361
|
{
|
|
@@ -364,7 +364,7 @@ const app: App = {
|
|
|
364
364
|
op: WidgetOperationTypes.APPEND,
|
|
365
365
|
element: (<LinkMenuItem label="Link 4" url="#" variant="navLink" />),
|
|
366
366
|
condition: {
|
|
367
|
-
active: ['slotShowcase'],
|
|
367
|
+
active: ['org.openedx.frontend.role.slotShowcase'],
|
|
368
368
|
}
|
|
369
369
|
},
|
|
370
370
|
]
|
package/shell/header/app.tsx
CHANGED
|
@@ -66,7 +66,7 @@ const config: App = {
|
|
|
66
66
|
element: (
|
|
67
67
|
<ProfileLinkMenuItem
|
|
68
68
|
label={messages['header.user.menu.profile']}
|
|
69
|
-
role="profile"
|
|
69
|
+
role="org.openedx.frontend.role.profile"
|
|
70
70
|
variant="dropdownItem"
|
|
71
71
|
/>
|
|
72
72
|
)
|
|
@@ -78,7 +78,7 @@ const config: App = {
|
|
|
78
78
|
element: (
|
|
79
79
|
<LinkMenuItem
|
|
80
80
|
label={messages['header.user.menu.account']}
|
|
81
|
-
role="account"
|
|
81
|
+
role="org.openedx.frontend.role.account"
|
|
82
82
|
variant="dropdownItem"
|
|
83
83
|
/>
|
|
84
84
|
)
|
|
@@ -90,7 +90,7 @@ const config: App = {
|
|
|
90
90
|
element: (
|
|
91
91
|
<LinkMenuItem
|
|
92
92
|
label={messages['header.user.menu.logout']}
|
|
93
|
-
role="logout"
|
|
93
|
+
role="org.openedx.frontend.role.logout"
|
|
94
94
|
variant="dropdownItem"
|
|
95
95
|
/>
|
|
96
96
|
)
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { createBrowserRouter } from 'react-router-dom';
|
|
2
|
+
import { getBasename } from '../../runtime/initialize';
|
|
3
|
+
import Shell from '../Shell';
|
|
4
|
+
import createRouter from './createRouter';
|
|
5
|
+
import getAppRoutes from './getAppRoutes';
|
|
6
|
+
|
|
7
|
+
jest.mock('react-router-dom', () => ({
|
|
8
|
+
createBrowserRouter: jest.fn(),
|
|
9
|
+
}));
|
|
10
|
+
|
|
11
|
+
jest.mock('../../runtime/initialize', () => ({
|
|
12
|
+
getBasename: jest.fn(),
|
|
13
|
+
}));
|
|
14
|
+
|
|
15
|
+
jest.mock('../Shell', () => jest.fn());
|
|
16
|
+
jest.mock('./getAppRoutes', () => jest.fn());
|
|
17
|
+
|
|
18
|
+
describe('createRouter', () => {
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
jest.clearAllMocks();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('should create a router with the correct configuration', () => {
|
|
24
|
+
const mockRoutes = [{ path: '/', element: <div>Home</div> }];
|
|
25
|
+
const mockBasename = '/base';
|
|
26
|
+
|
|
27
|
+
(getAppRoutes as jest.Mock).mockReturnValue(mockRoutes);
|
|
28
|
+
(getBasename as jest.Mock).mockReturnValue(mockBasename);
|
|
29
|
+
(createBrowserRouter as jest.Mock).mockReturnValue('fake router value');
|
|
30
|
+
|
|
31
|
+
const result = createRouter();
|
|
32
|
+
|
|
33
|
+
expect(getAppRoutes).toHaveBeenCalled();
|
|
34
|
+
expect(getBasename).toHaveBeenCalled();
|
|
35
|
+
expect(createBrowserRouter).toHaveBeenCalledWith(
|
|
36
|
+
[
|
|
37
|
+
{
|
|
38
|
+
Component: Shell,
|
|
39
|
+
children: mockRoutes,
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
{
|
|
43
|
+
basename: mockBasename,
|
|
44
|
+
}
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
// Just proving we actually return the router from createBrowserRouter.
|
|
48
|
+
expect(result).toEqual('fake router value');
|
|
49
|
+
});
|
|
50
|
+
});
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { RouteObject } from 'react-router';
|
|
2
|
+
import { getSiteConfig } from '../../runtime';
|
|
3
|
+
import getAppRoutes from './getAppRoutes';
|
|
4
|
+
|
|
5
|
+
jest.mock('../../runtime', () => ({
|
|
6
|
+
getSiteConfig: jest.fn(),
|
|
7
|
+
}));
|
|
8
|
+
|
|
9
|
+
describe('getAppRoutes', () => {
|
|
10
|
+
it('should return an empty array when no apps are configured', () => {
|
|
11
|
+
(getSiteConfig as jest.Mock).mockReturnValue({ apps: [] });
|
|
12
|
+
|
|
13
|
+
const routes = getAppRoutes();
|
|
14
|
+
|
|
15
|
+
expect(routes).toEqual([]);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('should flatten and return routes from configured apps', () => {
|
|
19
|
+
const mockApps = [
|
|
20
|
+
{
|
|
21
|
+
routes: [
|
|
22
|
+
{ path: '/page1', element: <div>Page 1</div> },
|
|
23
|
+
{ path: '/page2', element: <div>Page 2</div> }
|
|
24
|
+
]
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
routes: [
|
|
28
|
+
{ path: '/page3', element: <div>Page 3</div> }
|
|
29
|
+
]
|
|
30
|
+
},
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
(getSiteConfig as jest.Mock).mockReturnValue({ apps: mockApps });
|
|
34
|
+
|
|
35
|
+
const routes = getAppRoutes();
|
|
36
|
+
|
|
37
|
+
expect(routes).toEqual([
|
|
38
|
+
{ path: '/page1', element: <div>Page 1</div> },
|
|
39
|
+
{ path: '/page2', element: <div>Page 2</div> },
|
|
40
|
+
{ path: '/page3', element: <div>Page 3</div> }
|
|
41
|
+
]);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should ignore apps without routes', () => {
|
|
45
|
+
const mockRoutes: RouteObject[] = [
|
|
46
|
+
{ path: '/page1', element: <div>Page 1</div> },
|
|
47
|
+
];
|
|
48
|
+
const mockApps = [
|
|
49
|
+
{ routes: mockRoutes },
|
|
50
|
+
{ slots: [] },
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
(getSiteConfig as jest.Mock).mockReturnValue({ apps: mockApps });
|
|
54
|
+
|
|
55
|
+
const routes = getAppRoutes();
|
|
56
|
+
|
|
57
|
+
expect(routes).toEqual(mockRoutes);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
@@ -18,15 +18,15 @@ const siteConfig: SiteConfig = {
|
|
|
18
18
|
|
|
19
19
|
externalRoutes: [
|
|
20
20
|
{
|
|
21
|
-
role: 'profile',
|
|
21
|
+
role: 'org.openedx.frontend.role.profile',
|
|
22
22
|
url: 'http://apps.local.openedx.io:1995/profile/'
|
|
23
23
|
},
|
|
24
24
|
{
|
|
25
|
-
role: 'account',
|
|
25
|
+
role: 'org.openedx.frontend.role.account',
|
|
26
26
|
url: 'http://apps.local.openedx.io:1997/account/'
|
|
27
27
|
},
|
|
28
28
|
{
|
|
29
|
-
role: 'logout',
|
|
29
|
+
role: 'org.openedx.frontend.role.logout',
|
|
30
30
|
url: 'http://local.openedx.io:8000/logout'
|
|
31
31
|
},
|
|
32
32
|
],
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { EnvironmentTypes, SiteConfig } from '../types';
|
|
2
|
+
|
|
3
|
+
const siteConfig: SiteConfig = {
|
|
4
|
+
siteId: 'test',
|
|
5
|
+
siteName: 'Open 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
|
+
segmentKey: 'segment_whoa',
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export default siteConfig;
|