UNPKG

mod-arch-core

Version:

Core functionality and API utilities for modular architecture micro-frontend projects

331 lines 16.4 kB
import '@testing-library/jest-dom'; import * as React from 'react'; import { render, screen, act, waitFor } from '@testing-library/react'; import { ModularArchContext, ModularArchContextProvider } from '../../context/ModularArchContext'; import { DeploymentMode } from '../../utilities'; import * as useFetchStateModule from '../../utilities/useFetchState'; // Mock the utilities jest.mock('~/utilities/useFetchState'); jest.mock('~/api/k8s'); const mockUseFetchState = useFetchStateModule.useFetchState; const mockNamespaces = [{ name: 'namespace-2' }, { name: 'namespace-3' }, { name: 'namespace-1' }]; const createMockConfig = (deploymentMode = DeploymentMode.Standalone, mandatoryNamespace) => ({ deploymentMode, URL_PREFIX: 'model-registry', BFF_API_VERSION: 'v1', ...(mandatoryNamespace && { mandatoryNamespace }), }); describe('ModularArchContext', () => { const TestConsumer = () => { const context = React.useContext(ModularArchContext); if (!context) { return React.createElement("div", { "data-testid": "no-context" }, "No context"); } const { config, namespaces, namespacesLoaded, namespacesLoadError, preferredNamespace, initializationError, scriptLoaded, } = context; return (React.createElement("div", null, React.createElement("div", { "data-testid": "deployment-mode" }, config.deploymentMode), React.createElement("div", { "data-testid": "loading-state" }, namespacesLoaded.toString()), React.createElement("div", { "data-testid": "error-state" }, namespacesLoadError?.message || 'no-error'), React.createElement("div", { "data-testid": "init-error" }, initializationError?.message || 'no-init-error'), React.createElement("div", { "data-testid": "namespaces" }, namespaces.map((ns) => ns.name).join(',')), React.createElement("div", { "data-testid": "preferred" }, preferredNamespace?.name || 'none'), React.createElement("div", { "data-testid": "script-loaded" }, scriptLoaded.toString()))); }; beforeEach(() => { jest.clearAllMocks(); // Reset global window.centraldashboard delete global.window.centraldashboard; // Mock fetch for script loading global.fetch = jest.fn().mockResolvedValue({ ok: false }); }); afterEach(() => { jest.restoreAllMocks(); }); it('should provide initial empty state for standalone deployment', async () => { mockUseFetchState.mockReturnValue([[], true, undefined, jest.fn()]); const config = createMockConfig(); render(React.createElement(ModularArchContextProvider, { config: config }, React.createElement(TestConsumer, null))); expect(screen.getByTestId('deployment-mode')).toHaveTextContent('standalone'); expect(screen.getByTestId('loading-state')).toHaveTextContent('true'); expect(screen.getByTestId('error-state')).toHaveTextContent('no-error'); expect(screen.getByTestId('namespaces')).toHaveTextContent(''); expect(screen.getByTestId('preferred')).toHaveTextContent('none'); expect(screen.getByTestId('script-loaded')).toHaveTextContent('true'); }); it('should load namespaces and set first namespace as preferred', async () => { mockUseFetchState.mockReturnValue([mockNamespaces, true, undefined, jest.fn()]); const config = createMockConfig(); render(React.createElement(ModularArchContextProvider, { config: config }, React.createElement(TestConsumer, null))); expect(screen.getByTestId('loading-state')).toHaveTextContent('true'); expect(screen.getByTestId('error-state')).toHaveTextContent('no-error'); expect(screen.getByTestId('namespaces')).toHaveTextContent('namespace-1,namespace-2,namespace-3'); expect(screen.getByTestId('preferred')).toHaveTextContent('namespace-1'); }); it('should handle errors during namespace loading', async () => { const error = new Error('Failed to load namespaces'); mockUseFetchState.mockReturnValue([[], true, error, jest.fn()]); const config = createMockConfig(); render(React.createElement(ModularArchContextProvider, { config: config }, React.createElement(TestConsumer, null))); expect(screen.getByTestId('loading-state')).toHaveTextContent('true'); expect(screen.getByTestId('error-state')).toHaveTextContent('Failed to load namespaces'); expect(screen.getByTestId('namespaces')).toHaveTextContent(''); expect(screen.getByTestId('preferred')).toHaveTextContent('none'); }); it('should initialize central dashboard client when kubeflow', async () => { mockUseFetchState.mockReturnValue([mockNamespaces, true, undefined, jest.fn()]); // Mock fetch to resolve successfully for script loading global.fetch.mockResolvedValue({ ok: true }); // Mock window.centraldashboard before rendering const mockInit = jest.fn((callback) => { // Simulate the callback being called callback({ onNamespaceSelected: jest.fn(), }); }); global.window.centraldashboard = { CentralDashboardEventHandler: { init: mockInit, }, }; // Mock script loading const originalCreateElement = document.createElement; const mockScript = { src: '', async: true, onload: null, onerror: null, }; document.createElement = jest.fn((tagName) => { if (tagName === 'script') { return mockScript; } return originalCreateElement.call(document, tagName); }); const appendChild = jest.spyOn(document.head, 'appendChild').mockImplementation(() => { setTimeout(() => { if (mockScript.onload) { mockScript.onload.call(mockScript, new Event('load')); } }, 0); return mockScript; }); const config = createMockConfig(DeploymentMode.Kubeflow); render(React.createElement(ModularArchContextProvider, { config: config }, React.createElement(TestConsumer, null))); // Wait for script loading effect await waitFor(() => { expect(screen.getByTestId('preferred')).toHaveTextContent('namespace-1'); }); expect(mockInit).toHaveBeenCalled(); // Cleanup document.createElement = originalCreateElement; appendChild.mockRestore(); }); it('should update preferred namespace when selected', async () => { mockUseFetchState.mockReturnValue([mockNamespaces, true, undefined, jest.fn()]); const TestUpdater = () => { const context = React.useContext(ModularArchContext); React.useEffect(() => { if (context) { act(() => { context.updatePreferredNamespace({ name: 'namespace-2' }); }); } }, [context]); return null; }; const config = createMockConfig(); render(React.createElement(ModularArchContextProvider, { config: config }, React.createElement(TestConsumer, null), React.createElement(TestUpdater, null))); expect(screen.getByTestId('preferred')).toHaveTextContent('namespace-2'); }); it('should handle central dashboard initialization failure gracefully', async () => { mockUseFetchState.mockReturnValue([mockNamespaces, true, undefined, jest.fn()]); // Mock fetch to resolve successfully for script loading global.fetch.mockResolvedValue({ ok: true }); // Mock console.error to avoid test output noise const consoleSpy = jest.spyOn(console, 'error').mockImplementation(); // Mock window.centraldashboard with an init function that throws an error const error = new Error('Central dashboard initialization failed'); global.window.centraldashboard = { CentralDashboardEventHandler: { init: () => { throw error; }, }, }; // Mock script loading const originalCreateElement = document.createElement; const mockScript = { src: '', async: true, onload: null, onerror: null, }; document.createElement = jest.fn((tagName) => { if (tagName === 'script') { return mockScript; } return originalCreateElement.call(document, tagName); }); const appendChild = jest.spyOn(document.head, 'appendChild').mockImplementation(() => { // Simulate script loaded setTimeout(() => { if (mockScript.onload) { mockScript.onload(); } }, 0); return mockScript; }); const config = createMockConfig(DeploymentMode.Kubeflow); render(React.createElement(ModularArchContextProvider, { config: config }, React.createElement(TestConsumer, null))); // Wait for script loading and error handling await waitFor(() => { expect(consoleSpy).toHaveBeenCalledWith('Failed to initialize central dashboard client', error); // Wait for loading to disappear (scriptLoaded = true) expect(screen.queryByText('Loading...')).not.toBeInTheDocument(); }); expect(screen.getByTestId('init-error')).toHaveTextContent('Central dashboard initialization failed'); expect(screen.getByTestId('preferred')).toHaveTextContent('namespace-1'); // Cleanup consoleSpy.mockRestore(); document.createElement = originalCreateElement; appendChild.mockRestore(); }); it('should handle multiple namespace updates correctly', async () => { mockUseFetchState.mockReturnValue([mockNamespaces, true, undefined, jest.fn()]); const TestMultipleUpdates = () => { const context = React.useContext(ModularArchContext); React.useEffect(() => { if (context) { act(() => { context.updatePreferredNamespace({ name: 'namespace-2' }); context.updatePreferredNamespace({ name: 'namespace-3' }); context.updatePreferredNamespace({ name: 'namespace-1' }); }); } }, [context]); return null; }; const config = createMockConfig(); render(React.createElement(ModularArchContextProvider, { config: config }, React.createElement(TestConsumer, null), React.createElement(TestMultipleUpdates, null))); expect(screen.getByTestId('preferred')).toHaveTextContent('namespace-1'); }); it('should show loading spinner when script is not loaded for kubeflow integration', async () => { mockUseFetchState.mockReturnValue([mockNamespaces, true, undefined, jest.fn()]); // Mock fetch to simulate script loading delay let resolvePromise; const promise = new Promise((resolve) => { resolvePromise = resolve; }); global.fetch.mockReturnValue(promise); const config = createMockConfig(DeploymentMode.Kubeflow); render(React.createElement(ModularArchContextProvider, { config: config }, React.createElement(TestConsumer, null))); // Should show loading text expect(screen.getByText('Loading...')).toBeInTheDocument(); expect(screen.queryByTestId('deployment-mode')).not.toBeInTheDocument(); // Resolve the promise to simulate script loading act(() => { resolvePromise({ ok: true }); }); }); it('should throw error when used outside provider', () => { const TestConsumerWithError = () => { const context = React.useContext(ModularArchContext); return React.createElement("div", null, context ? 'Has context' : 'No context'); }; render(React.createElement(TestConsumerWithError, null)); expect(screen.getByText('No context')).toBeInTheDocument(); }); describe('mandatory namespace functionality', () => { it('should use mandatory namespace when provided in config', async () => { const mandatoryNamespace = 'mandatory-namespace'; // Mock useFetchState to return only the mandatory namespace mockUseFetchState.mockReturnValue([ [{ name: mandatoryNamespace }], true, undefined, jest.fn(), ]); const config = createMockConfig(DeploymentMode.Federated); render(React.createElement(ModularArchContextProvider, { config: config }, React.createElement(TestConsumer, null))); expect(screen.getByTestId('namespaces')).toHaveTextContent(mandatoryNamespace); expect(screen.getByTestId('preferred')).toHaveTextContent(mandatoryNamespace); }); it('should not fetch namespaces when mandatory namespace is set', async () => { const mandatoryNamespace = 'mandatory-namespace'; // Mock useFetchState to return only the mandatory namespace mockUseFetchState.mockReturnValue([ [{ name: mandatoryNamespace }], true, undefined, jest.fn(), ]); const config = createMockConfig(DeploymentMode.Federated, mandatoryNamespace); render(React.createElement(ModularArchContextProvider, { config: config }, React.createElement(TestConsumer, null))); // Should show the mandatory namespace as loaded expect(screen.getByTestId('loading-state')).toHaveTextContent('true'); expect(screen.getByTestId('namespaces')).toHaveTextContent(mandatoryNamespace); expect(screen.getByTestId('preferred')).toHaveTextContent(mandatoryNamespace); }); it('should not use kubeflow namespace loader when mandatory namespace is set', async () => { const mandatoryNamespace = 'mandatory-namespace'; mockUseFetchState.mockReturnValue([ [{ name: mandatoryNamespace }], true, undefined, jest.fn(), ]); // Mock fetch to resolve successfully for script loading global.fetch.mockResolvedValue({ ok: true }); // Mock script loading const originalCreateElement = document.createElement; const mockScript = { src: '', async: true, onload: null, onerror: null, }; document.createElement = jest.fn((tagName) => { if (tagName === 'script') { return mockScript; } return originalCreateElement.call(document, tagName); }); const appendChild = jest.spyOn(document.head, 'appendChild').mockImplementation(() => { setTimeout(() => { if (mockScript.onload) { mockScript.onload.call(mockScript, new Event('load')); } }, 0); return mockScript; }); // Mock kubeflow window object global.window.centraldashboard = { CentralDashboardEventHandler: { init: jest.fn(), }, }; const config = createMockConfig(DeploymentMode.Kubeflow, mandatoryNamespace); render(React.createElement(ModularArchContextProvider, { config: config }, React.createElement(TestConsumer, null))); await waitFor(() => { expect(screen.getByTestId('preferred')).toHaveTextContent(mandatoryNamespace); }); // Cleanup document.createElement = originalCreateElement; appendChild.mockRestore(); }); }); }); //# sourceMappingURL=ModularArchContext.test.js.map