diff --git a/src/core/server/rendering/bootstrap/bootstrap_renderer.test.mocks.ts b/src/core/server/rendering/bootstrap/bootstrap_renderer.test.mocks.ts new file mode 100644 index 0000000000000..2b3a6788a465d --- /dev/null +++ b/src/core/server/rendering/bootstrap/bootstrap_renderer.test.mocks.ts @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export const renderTemplateMock = jest.fn(); +jest.doMock('./render_template', () => ({ + renderTemplate: renderTemplateMock, +})); + +export const getThemeTagMock = jest.fn(); +jest.doMock('./get_theme_tag', () => ({ + getThemeTag: getThemeTagMock, +})); + +export const getPluginsBundlePathsMock = jest.fn(); +jest.doMock('./get_plugin_bundle_paths', () => ({ + getPluginsBundlePaths: getPluginsBundlePathsMock, +})); + +export const getJsDependencyPathsMock = jest.fn(); +jest.doMock('./get_js_dependency_paths', () => ({ + getJsDependencyPaths: getJsDependencyPathsMock, +})); diff --git a/src/core/server/rendering/bootstrap/bootstrap_renderer.test.ts b/src/core/server/rendering/bootstrap/bootstrap_renderer.test.ts new file mode 100644 index 0000000000000..3803d38a968c1 --- /dev/null +++ b/src/core/server/rendering/bootstrap/bootstrap_renderer.test.ts @@ -0,0 +1,241 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { + renderTemplateMock, + getPluginsBundlePathsMock, + getThemeTagMock, + getJsDependencyPathsMock, +} from './bootstrap_renderer.test.mocks'; + +import { PackageInfo } from '@kbn/config'; +import { UiPlugins } from '../../plugins'; +import { httpServiceMock } from '../../http/http_service.mock'; +import { httpServerMock } from '../../http/http_server.mocks'; +import { AuthStatus } from '../../http'; +import { uiSettingsServiceMock } from '../../ui_settings/ui_settings_service.mock'; +import { bootstrapRendererFactory, BootstrapRenderer } from './bootstrap_renderer'; + +const createPackageInfo = (parts: Partial = {}): PackageInfo => ({ + branch: 'master', + buildNum: 42, + buildSha: 'buildSha', + dist: false, + version: '8.0.0', + ...parts, +}); + +const createUiPlugins = (): UiPlugins => ({ + public: new Map(), + internal: new Map(), + browserConfigs: new Map(), +}); + +describe('bootstrapRenderer', () => { + let auth: ReturnType; + let uiSettingsClient: ReturnType; + let renderer: BootstrapRenderer; + let uiPlugins: UiPlugins; + let packageInfo: PackageInfo; + + beforeEach(() => { + auth = httpServiceMock.createAuth(); + uiSettingsClient = uiSettingsServiceMock.createClient(); + uiPlugins = createUiPlugins(); + packageInfo = createPackageInfo(); + + getThemeTagMock.mockReturnValue('v8light'); + getPluginsBundlePathsMock.mockReturnValue(new Map()); + renderTemplateMock.mockReturnValue('__rendered__'); + getJsDependencyPathsMock.mockReturnValue([]); + + renderer = bootstrapRendererFactory({ + auth, + packageInfo, + uiPlugins, + serverBasePath: '/base-path', + }); + }); + + afterEach(() => { + getThemeTagMock.mockReset(); + getPluginsBundlePathsMock.mockReset(); + renderTemplateMock.mockReset(); + getJsDependencyPathsMock.mockReset(); + }); + + describe('when the auth status is `authenticated`', () => { + beforeEach(() => { + auth.get.mockReturnValue({ + status: 'authenticated' as AuthStatus, + state: {}, + }); + }); + + it('calls uiSettingsClient.get with the correct parameters', async () => { + const request = httpServerMock.createKibanaRequest(); + + await renderer({ + request, + uiSettingsClient, + }); + + expect(uiSettingsClient.get).toHaveBeenCalledTimes(2); + expect(uiSettingsClient.get).toHaveBeenCalledWith('theme:darkMode'); + expect(uiSettingsClient.get).toHaveBeenCalledWith('theme:version'); + }); + + it('calls getThemeTag with the correct parameters', async () => { + uiSettingsClient.get.mockImplementation((settingName) => { + return Promise.resolve(settingName === 'theme:darkMode' ? true : 'v8'); + }); + + const request = httpServerMock.createKibanaRequest(); + + await renderer({ + request, + uiSettingsClient, + }); + + expect(getThemeTagMock).toHaveBeenCalledTimes(1); + expect(getThemeTagMock).toHaveBeenCalledWith({ + themeVersion: 'v8', + darkMode: true, + }); + }); + }); + + describe('when the auth status is `unknown`', () => { + beforeEach(() => { + auth.get.mockReturnValue({ + status: 'unknown' as AuthStatus, + state: {}, + }); + }); + + it('calls uiSettingsClient.get with the correct parameters', async () => { + const request = httpServerMock.createKibanaRequest(); + + await renderer({ + request, + uiSettingsClient, + }); + + expect(uiSettingsClient.get).toHaveBeenCalledTimes(2); + expect(uiSettingsClient.get).toHaveBeenCalledWith('theme:darkMode'); + expect(uiSettingsClient.get).toHaveBeenCalledWith('theme:version'); + }); + + it('calls getThemeTag with the correct parameters', async () => { + uiSettingsClient.get.mockImplementation((settingName) => { + return Promise.resolve(settingName === 'theme:darkMode' ? true : 'v8'); + }); + + const request = httpServerMock.createKibanaRequest(); + + await renderer({ + request, + uiSettingsClient, + }); + + expect(getThemeTagMock).toHaveBeenCalledTimes(1); + expect(getThemeTagMock).toHaveBeenCalledWith({ + themeVersion: 'v8', + darkMode: true, + }); + }); + }); + + describe('when the auth status is `unauthenticated`', () => { + beforeEach(() => { + auth.get.mockReturnValue({ + status: 'unauthenticated' as AuthStatus, + state: {}, + }); + }); + + it('does not call uiSettingsClient.get', async () => { + const request = httpServerMock.createKibanaRequest(); + + await renderer({ + request, + uiSettingsClient, + }); + + expect(uiSettingsClient.get).not.toHaveBeenCalled(); + }); + + it('calls getThemeTag with the default parameters', async () => { + const request = httpServerMock.createKibanaRequest(); + + await renderer({ + request, + uiSettingsClient, + }); + + expect(getThemeTagMock).toHaveBeenCalledTimes(1); + expect(getThemeTagMock).toHaveBeenCalledWith({ + themeVersion: 'v7', + darkMode: false, + }); + }); + }); + + it('calls getPluginsBundlePaths with the correct parameters', async () => { + const request = httpServerMock.createKibanaRequest(); + + await renderer({ + request, + uiSettingsClient, + }); + + expect(getPluginsBundlePathsMock).toHaveBeenCalledTimes(1); + expect(getPluginsBundlePathsMock).toHaveBeenCalledWith({ + uiPlugins, + regularBundlePath: '/base-path/42/bundles', + }); + }); + + // here + it('calls getJsDependencyPaths with the correct parameters', async () => { + const pluginsBundlePaths = new Map(); + + getPluginsBundlePathsMock.mockReturnValue(pluginsBundlePaths); + const request = httpServerMock.createKibanaRequest(); + + await renderer({ + request, + uiSettingsClient, + }); + + expect(getJsDependencyPathsMock).toHaveBeenCalledTimes(1); + expect(getJsDependencyPathsMock).toHaveBeenCalledWith( + '/base-path/42/bundles', + pluginsBundlePaths + ); + }); + + it('calls renderTemplate with the correct parameters', async () => { + getThemeTagMock.mockReturnValue('customThemeTag'); + getJsDependencyPathsMock.mockReturnValue(['path-1', 'path-2']); + + const request = httpServerMock.createKibanaRequest(); + + await renderer({ + request, + uiSettingsClient, + }); + + expect(renderTemplateMock).toHaveBeenCalledTimes(1); + expect(renderTemplateMock).toHaveBeenCalledWith({ + themeTag: 'customThemeTag', + jsDependencyPaths: ['path-1', 'path-2'], + publicPathMap: expect.any(String), + }); + }); +}); diff --git a/src/core/server/rendering/bootstrap/bootstrap_renderer.ts b/src/core/server/rendering/bootstrap/bootstrap_renderer.ts index adb510c6f0512..cff593e5c5aa9 100644 --- a/src/core/server/rendering/bootstrap/bootstrap_renderer.ts +++ b/src/core/server/rendering/bootstrap/bootstrap_renderer.ts @@ -7,12 +7,12 @@ */ import { createHash } from 'crypto'; -import * as UiSharedDeps from '@kbn/ui-shared-deps'; import { PackageInfo } from '@kbn/config'; import { UiPlugins } from '../../plugins'; import { IUiSettingsClient } from '../../ui_settings'; import { HttpAuth, KibanaRequest } from '../../http'; import { getPluginsBundlePaths } from './get_plugin_bundle_paths'; +import { getJsDependencyPaths } from './get_js_dependency_paths'; import { getThemeTag } from './get_theme_tag'; import { renderTemplate } from './render_template'; @@ -72,14 +72,7 @@ export const bootstrapRendererFactory: BootstrapRendererFactory = ({ regularBundlePath, }); - const jsDependencyPaths = [ - ...UiSharedDeps.jsDepFilenames.map( - (filename) => `${regularBundlePath}/kbn-ui-shared-deps/${filename}` - ), - `${regularBundlePath}/kbn-ui-shared-deps/${UiSharedDeps.jsFilename}`, - `${regularBundlePath}/core/core.entry.js`, - ...[...bundlePaths.values()].map((plugin) => plugin.bundlePath), - ]; + const jsDependencyPaths = getJsDependencyPaths(regularBundlePath, bundlePaths); // These paths should align with the bundle routes configured in // src/optimize/bundles_route/bundles_route.ts diff --git a/src/core/server/rendering/bootstrap/get_js_dependency_paths.test.ts b/src/core/server/rendering/bootstrap/get_js_dependency_paths.test.ts new file mode 100644 index 0000000000000..964e64186459f --- /dev/null +++ b/src/core/server/rendering/bootstrap/get_js_dependency_paths.test.ts @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { getJsDependencyPaths } from './get_js_dependency_paths'; +import type { PluginInfo } from './get_plugin_bundle_paths'; + +describe('getJsDependencyPaths', () => { + it('returns the correct list of paths', () => { + const bundlePaths = new Map(); + bundlePaths.set('plugin1', { + bundlePath: 'plugin1/bundle-path.js', + publicPath: 'plugin1/public-path', + }); + bundlePaths.set('plugin2', { + bundlePath: 'plugin2/bundle-path.js', + publicPath: 'plugin2/public-path', + }); + + expect(getJsDependencyPaths('/regular-bundle-path', bundlePaths)).toEqual([ + '/regular-bundle-path/kbn-ui-shared-deps/kbn-ui-shared-deps.@elastic.js', + '/regular-bundle-path/kbn-ui-shared-deps/kbn-ui-shared-deps.js', + '/regular-bundle-path/core/core.entry.js', + 'plugin1/bundle-path.js', + 'plugin2/bundle-path.js', + ]); + }); +}); diff --git a/src/core/server/rendering/bootstrap/get_js_dependency_paths.ts b/src/core/server/rendering/bootstrap/get_js_dependency_paths.ts new file mode 100644 index 0000000000000..72c7b84fbee2e --- /dev/null +++ b/src/core/server/rendering/bootstrap/get_js_dependency_paths.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import * as UiSharedDeps from '@kbn/ui-shared-deps'; +import type { PluginInfo } from './get_plugin_bundle_paths'; + +export const getJsDependencyPaths = ( + regularBundlePath: string, + bundlePaths: Map +) => { + return [ + ...UiSharedDeps.jsDepFilenames.map( + (filename) => `${regularBundlePath}/kbn-ui-shared-deps/${filename}` + ), + `${regularBundlePath}/kbn-ui-shared-deps/${UiSharedDeps.jsFilename}`, + `${regularBundlePath}/core/core.entry.js`, + ...[...bundlePaths.values()].map((plugin) => plugin.bundlePath), + ]; +}; diff --git a/src/core/server/rendering/bootstrap/get_plugin_bundle_paths.ts b/src/core/server/rendering/bootstrap/get_plugin_bundle_paths.ts index a42f7b9974f57..c8291b2720a92 100644 --- a/src/core/server/rendering/bootstrap/get_plugin_bundle_paths.ts +++ b/src/core/server/rendering/bootstrap/get_plugin_bundle_paths.ts @@ -8,7 +8,7 @@ import { UiPlugins } from '../../plugins'; -interface PluginInfo { +export interface PluginInfo { publicPath: string; bundlePath: string; }