Skip to content

Commit bbebb2a

Browse files
committed
Revamp modulefederation config for testing
1 parent 521b2b4 commit bbebb2a

File tree

5 files changed

+115
-8
lines changed

5 files changed

+115
-8
lines changed

frontend/config/moduleFederation.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@ const readModuleFederationConfigFromPackages = () => {
4242
};
4343

4444
const getModuleFederationConfig = () => {
45+
// Disable module federation for Cypress tests
46+
if (process.env.CYPRESS_TESTS === 'true') {
47+
return [];
48+
}
49+
4550
if (process.env.MODULE_FEDERATION_CONFIG) {
4651
try {
4752
return JSON.parse(process.env.MODULE_FEDERATION_CONFIG);

frontend/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,10 @@
5454
"cypress:run:mock:coverage": "rimraf src/__tests__/cypress/coverage src/__tests__/cypress/.nyc_output && CY_COVERAGE=true npm run cypress:run:mock -- {@}",
5555
"cypress:open:record": "CY_RECORD=1 npm run cypress:open",
5656
"cypress:run:record": "CY_RECORD=1 npm run cypress:run && npm run cypress:format",
57-
"cypress:server:build": "ODH_DIST_DIR=./public-cypress POLL_INTERVAL=9999999 FAST_POLL_INTERVAL=9999999 WS_HOSTNAME=localhost:9002 npm run build",
58-
"cypress:server:build:coverage": "COVERAGE=true npm run cypress:server:build",
57+
"cypress:server:build": "ODH_DIST_DIR=./public-cypress POLL_INTERVAL=9999999 FAST_POLL_INTERVAL=9999999 WS_HOSTNAME=localhost:9002 CYPRESS_TESTS=true npm run build",
58+
"cypress:server:build:coverage": "COVERAGE=true CYPRESS_TESTS=true npm run cypress:server:build",
5959
"cypress:server": "serve ./public-cypress -p 9001 -s -L",
60-
"cypress:server:dev": "POLL_INTERVAL=9999999 FAST_POLL_INTERVAL=9999999 ODH_PORT=9001 WS_HOSTNAME=localhost:9002 npm run start:dev",
60+
"cypress:server:dev": "POLL_INTERVAL=9999999 FAST_POLL_INTERVAL=9999999 ODH_PORT=9001 WS_HOSTNAME=localhost:9002 CYPRESS_TESTS=true npm run start:dev",
6161
"cypress:format": "prettier --write src/__tests__/**/*.snap.json",
6262
"test:cypress:e2e": "cd src/__tests__/cypress && npx cypress run --env grepTags=\"${CY_TEST_TAGS}\",skipTags=\"${CY_SKIP_TAGS}\" --browser chrome",
6363
"test:cypress:e2e:open": "cd src/__tests__/cypress && npx cypress open --env grepTags=\"${CY_TEST_TAGS}\",skipTags=\"${CY_SKIP_TAGS}\""

frontend/src/__tests__/cypress/cypress/support/e2e.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import '@cypress/code-coverage/support';
2020
import 'cypress-mochawesome-reporter/register';
2121
import 'cypress-plugin-steps';
2222
import './commands';
23+
import '#~/__tests__/cypress/cypress/utils/moduleFederationMock';
2324
import { asProjectAdminUser } from '#~/__tests__/cypress/cypress/utils/mockUsers';
2425
import { mockDscStatus } from '#~/__mocks__/mockDscStatus';
2526
import { addCommands as webSocketsAddCommands } from './websockets';
@@ -325,6 +326,10 @@ beforeEach(function beforeEachHook(this: Mocha.Context) {
325326
if (Cypress.env('MOCK')) {
326327
// Fallback: return 404 for all API requests.
327328
cy.intercept({ pathname: '/api/**' }, { statusCode: 404 });
329+
330+
// Set up module federation mocks
331+
cy.setupModuleFederationMocks(['modelRegistry']);
332+
328333
// Default intercepts.
329334
cy.interceptOdh('GET /api/dsc/status', mockDscStatus({}));
330335
asProjectAdminUser();
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/**
2+
* Module Federation Mock Utilities for Cypress Tests
3+
*
4+
* This file provides utilities to mock module federation behavior
5+
* during Cypress tests to prevent runtime errors.
6+
*/
7+
8+
/**
9+
* Mock remote entry content that provides a basic module federation interface
10+
*/
11+
export const createMockRemoteEntry = (moduleName: string): string => `
12+
// Mock module federation remote entry for ${moduleName}
13+
(function() {
14+
if (!window.__FEDERATION__) {
15+
window.__FEDERATION__ = {};
16+
}
17+
18+
if (!window.__FEDERATION__.moduleMap) {
19+
window.__FEDERATION__.moduleMap = {};
20+
}
21+
22+
// Mock the module exports
23+
window.__FEDERATION__.moduleMap['${moduleName}'] = {
24+
get: function(scope) {
25+
if (scope === 'extensions') {
26+
return Promise.resolve({
27+
default: []
28+
});
29+
}
30+
return Promise.resolve({});
31+
},
32+
init: function() {
33+
return Promise.resolve();
34+
}
35+
};
36+
37+
// Export for ES modules compatibility
38+
if (typeof module !== 'undefined' && module.exports) {
39+
module.exports = window.__FEDERATION__.moduleMap['${moduleName}'];
40+
}
41+
})();
42+
`;
43+
44+
/**
45+
* Cypress command to set up module federation mocks
46+
*/
47+
export const setupModuleFederationMocks = (modules: string[] = ['modelRegistry']): void => {
48+
modules.forEach((moduleName) => {
49+
// Mock the remote entry file
50+
cy.intercept(
51+
{ pathname: `/_mf/${moduleName}/remoteEntry.js` },
52+
{
53+
statusCode: 200,
54+
headers: { 'content-type': 'application/javascript' },
55+
body: createMockRemoteEntry(moduleName),
56+
},
57+
);
58+
59+
// Mock the extensions endpoint
60+
cy.intercept(
61+
{ url: `**/_mf/${moduleName}/**/extensions` },
62+
{
63+
statusCode: 200,
64+
headers: { 'content-type': 'application/javascript' },
65+
body: 'export default [];',
66+
},
67+
);
68+
});
69+
70+
// Mock any other module federation paths with 404
71+
cy.intercept({ pathname: '/_mf/**' }, { statusCode: 404 });
72+
};
73+
74+
/**
75+
* Add the command to Cypress
76+
*/
77+
// eslint-disable-next-line @typescript-eslint/no-namespace
78+
declare global {
79+
// eslint-disable-next-line @typescript-eslint/no-namespace
80+
namespace Cypress {
81+
interface Chainable {
82+
setupModuleFederationMocks: (modules?: string[]) => Chainable<void>;
83+
}
84+
}
85+
}
86+
87+
Cypress.Commands.add('setupModuleFederationMocks', setupModuleFederationMocks);

frontend/src/plugins/useAppExtensions.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,18 +24,24 @@ const initRemotes = (remotes: MFConfig[]) => {
2424
};
2525

2626
const loadModuleExtensions = (moduleName: string): Promise<Record<string, Extension[]>> =>
27-
loadRemote<{ default: Extension[] }>(`${moduleName}/extensions`).then((result) => ({
28-
[moduleName]: result ? result.default : [],
29-
}));
27+
loadRemote<{ default: Extension[] }>(`${moduleName}/extensions`)
28+
.then((result) => ({
29+
[moduleName]: result ? result.default : [],
30+
}))
31+
.catch((error) => {
32+
// eslint-disable-next-line no-console
33+
console.warn(`Failed to load module extensions for ${moduleName}:`, error);
34+
return { [moduleName]: [] };
35+
});
3036

3137
export const useAppExtensions = (): [Record<string, Extension[]>, boolean] => {
3238
const [appExtensions, setAppExtensions] = React.useState<Record<string, Extension[]>>({});
3339
const [loaded, setLoaded] = React.useState(!MF_CONFIG);
3440

3541
React.useEffect(() => {
3642
if (MF_CONFIG) {
37-
const remotes: MFConfig[] = JSON.parse(MF_CONFIG);
3843
try {
44+
const remotes: MFConfig[] = JSON.parse(MF_CONFIG);
3945
if (remotes.length > 0) {
4046
initRemotes(remotes);
4147
allSettledPromises(remotes.map((r) => loadModuleExtensions(r.name)))
@@ -46,13 +52,17 @@ export const useAppExtensions = (): [Record<string, Extension[]>, boolean] => {
4652
);
4753
}
4854
})
55+
.catch((error) => {
56+
// eslint-disable-next-line no-console
57+
console.warn('Error loading module federation extensions:', error);
58+
})
4959
.finally(() => setLoaded(true));
5060
} else {
5161
setLoaded(true);
5262
}
5363
} catch (error) {
5464
// eslint-disable-next-line no-console
55-
console.error('Error parsing module federation config:', error);
65+
console.warn('Error with module federation setup:', error);
5666
setLoaded(true);
5767
}
5868
}

0 commit comments

Comments
 (0)