From eeb005510c1b31c646fcedfcd624e3f67dc64e76 Mon Sep 17 00:00:00 2001 From: Marta Bondyra Date: Thu, 21 Oct 2021 22:38:19 +0200 Subject: [PATCH] [Lens] cleanup divide mock file to smaller pieces (#115925) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- x-pack/plugins/lens/public/mocks.tsx | 540 ------------------ .../lens/public/mocks/data_plugin_mock.ts | 122 ++++ .../lens/public/mocks/datasource_mock.ts | 78 +++ .../public/mocks/expression_renderer_mock.tsx | 16 + x-pack/plugins/lens/public/mocks/index.ts | 42 ++ .../lens/public/mocks/lens_plugin_mock.tsx | 31 + .../lens/public/mocks/services_mock.tsx | 143 +++++ .../plugins/lens/public/mocks/store_mocks.tsx | 140 +++++ .../lens/public/mocks/visualization_mock.ts | 63 ++ 9 files changed, 635 insertions(+), 540 deletions(-) delete mode 100644 x-pack/plugins/lens/public/mocks.tsx create mode 100644 x-pack/plugins/lens/public/mocks/data_plugin_mock.ts create mode 100644 x-pack/plugins/lens/public/mocks/datasource_mock.ts create mode 100644 x-pack/plugins/lens/public/mocks/expression_renderer_mock.tsx create mode 100644 x-pack/plugins/lens/public/mocks/index.ts create mode 100644 x-pack/plugins/lens/public/mocks/lens_plugin_mock.tsx create mode 100644 x-pack/plugins/lens/public/mocks/services_mock.tsx create mode 100644 x-pack/plugins/lens/public/mocks/store_mocks.tsx create mode 100644 x-pack/plugins/lens/public/mocks/visualization_mock.ts diff --git a/x-pack/plugins/lens/public/mocks.tsx b/x-pack/plugins/lens/public/mocks.tsx deleted file mode 100644 index 5c285f70b2ed9..0000000000000 --- a/x-pack/plugins/lens/public/mocks.tsx +++ /dev/null @@ -1,540 +0,0 @@ -/* - * 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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -// eslint-disable-next-line import/no-extraneous-dependencies -import { ReactWrapper } from 'enzyme'; -// eslint-disable-next-line import/no-extraneous-dependencies -import { mountWithIntl as mount } from '@kbn/test/jest'; -import { Observable, Subject } from 'rxjs'; -import { coreMock } from 'src/core/public/mocks'; -import moment from 'moment'; -import { Provider } from 'react-redux'; -import { act } from 'react-dom/test-utils'; -import { ReactExpressionRendererProps } from 'src/plugins/expressions/public'; -import { PreloadedState } from '@reduxjs/toolkit'; -import { LensPublicStart } from '.'; -import { visualizationTypes } from './xy_visualization/types'; -import { navigationPluginMock } from '../../../../src/plugins/navigation/public/mocks'; -import { LensAppServices } from './app_plugin/types'; -import { DOC_TYPE, layerTypes } from '../common'; -import { DataPublicPluginStart, esFilters, UI_SETTINGS } from '../../../../src/plugins/data/public'; -import { inspectorPluginMock } from '../../../../src/plugins/inspector/public/mocks'; -import { spacesPluginMock } from '../../spaces/public/mocks'; -import { dashboardPluginMock } from '../../../../src/plugins/dashboard/public/mocks'; -import type { - LensByValueInput, - LensByReferenceInput, - ResolvedLensSavedObjectAttributes, -} from './embeddable/embeddable'; -import { - mockAttributeService, - createEmbeddableStateTransferMock, -} from '../../../../src/plugins/embeddable/public/mocks'; -import { fieldFormatsServiceMock } from '../../../../src/plugins/field_formats/public/mocks'; -import type { LensAttributeService } from './lens_attribute_service'; -import type { EmbeddableStateTransfer } from '../../../../src/plugins/embeddable/public'; - -import { - makeConfigureStore, - LensAppState, - LensState, - LensStoreDeps, -} from './state_management/index'; -import { getResolvedDateRange } from './utils'; -import { presentationUtilPluginMock } from '../../../../src/plugins/presentation_util/public/mocks'; -import { - DatasourcePublicAPI, - Datasource, - Visualization, - FramePublicAPI, - FrameDatasourceAPI, - DatasourceMap, - VisualizationMap, -} from './types'; -import { getLensInspectorService } from './lens_inspector_service'; - -export function mockDatasourceStates() { - return { - testDatasource: { - state: {}, - isLoading: false, - }, - }; -} - -export function createMockVisualization(id = 'testVis'): jest.Mocked { - return { - id, - clearLayer: jest.fn((state, _layerId) => state), - removeLayer: jest.fn(), - getLayerIds: jest.fn((_state) => ['layer1']), - getSupportedLayers: jest.fn(() => [{ type: layerTypes.DATA, label: 'Data Layer' }]), - getLayerType: jest.fn((_state, _layerId) => layerTypes.DATA), - visualizationTypes: [ - { - icon: 'empty', - id, - label: 'TEST', - groupLabel: `${id}Group`, - }, - ], - appendLayer: jest.fn(), - getVisualizationTypeId: jest.fn((_state) => 'empty'), - getDescription: jest.fn((_state) => ({ label: '' })), - switchVisualizationType: jest.fn((_, x) => x), - getSuggestions: jest.fn((_options) => []), - initialize: jest.fn((_frame, _state?) => ({ newState: 'newState' })), - getConfiguration: jest.fn((props) => ({ - groups: [ - { - groupId: 'a', - groupLabel: 'a', - layerId: 'layer1', - supportsMoreColumns: true, - accessors: [], - filterOperations: jest.fn(() => true), - dataTestSubj: 'mockVisA', - }, - ], - })), - toExpression: jest.fn((_state, _frame) => null), - toPreviewExpression: jest.fn((_state, _frame) => null), - - setDimension: jest.fn(), - removeDimension: jest.fn(), - getErrorMessages: jest.fn((_state) => undefined), - renderDimensionEditor: jest.fn(), - }; -} - -export const visualizationMap = { - testVis: createMockVisualization(), - testVis2: createMockVisualization(), -}; - -export type DatasourceMock = jest.Mocked & { - publicAPIMock: jest.Mocked; -}; - -export function createMockDatasource(id: string): DatasourceMock { - const publicAPIMock: jest.Mocked = { - datasourceId: id, - getTableSpec: jest.fn(() => []), - getOperationForColumnId: jest.fn(), - }; - - return { - id: 'testDatasource', - clearLayer: jest.fn((state, _layerId) => state), - getDatasourceSuggestionsForField: jest.fn((_state, _item, filterFn) => []), - getDatasourceSuggestionsForVisualizeField: jest.fn((_state, _indexpatternId, _fieldName) => []), - getDatasourceSuggestionsFromCurrentState: jest.fn((_state) => []), - getPersistableState: jest.fn((x) => ({ - state: x, - savedObjectReferences: [{ type: 'index-pattern', id: 'mockip', name: 'mockip' }], - })), - getPublicAPI: jest.fn().mockReturnValue(publicAPIMock), - initialize: jest.fn((_state?) => Promise.resolve()), - renderDataPanel: jest.fn(), - renderLayerPanel: jest.fn(), - toExpression: jest.fn((_frame, _state) => null), - insertLayer: jest.fn((_state, _newLayerId) => ({})), - removeLayer: jest.fn((_state, _layerId) => {}), - removeColumn: jest.fn((props) => {}), - getLayers: jest.fn((_state) => []), - uniqueLabels: jest.fn((_state) => ({})), - renderDimensionTrigger: jest.fn(), - renderDimensionEditor: jest.fn(), - getDropProps: jest.fn(), - onDrop: jest.fn(), - - // this is an additional property which doesn't exist on real datasources - // but can be used to validate whether specific API mock functions are called - publicAPIMock, - getErrorMessages: jest.fn((_state) => undefined), - checkIntegrity: jest.fn((_state) => []), - isTimeBased: jest.fn(), - isValidColumn: jest.fn(), - }; -} - -export const mockDatasource: DatasourceMock = createMockDatasource('testDatasource'); -export const mockDatasource2: DatasourceMock = createMockDatasource('testDatasource2'); - -export const datasourceMap = { - testDatasource2: mockDatasource2, - testDatasource: mockDatasource, -}; - -export function createExpressionRendererMock(): jest.Mock< - React.ReactElement, - [ReactExpressionRendererProps] -> { - return jest.fn((_) => ); -} - -export type FrameMock = jest.Mocked; -export function createMockFramePublicAPI(): FrameMock { - return { - datasourceLayers: {}, - }; -} - -export type FrameDatasourceMock = jest.Mocked; -export function createMockFrameDatasourceAPI(): FrameDatasourceMock { - return { - datasourceLayers: {}, - dateRange: { fromDate: 'now-7d', toDate: 'now' }, - query: { query: '', language: 'lucene' }, - filters: [], - }; -} - -export type Start = jest.Mocked; - -const createStartContract = (): Start => { - const startContract: Start = { - EmbeddableComponent: jest.fn(() => { - return Lens Embeddable Component; - }), - SaveModalComponent: jest.fn(() => { - return Lens Save Modal Component; - }), - canUseEditor: jest.fn(() => true), - navigateToPrefilledEditor: jest.fn(), - getXyVisTypes: jest.fn().mockReturnValue(new Promise((resolve) => resolve(visualizationTypes))), - }; - return startContract; -}; - -export const lensPluginMock = { - createStartContract, -}; - -export const defaultDoc = { - savedObjectId: '1234', - title: 'An extremely cool default document!', - expression: 'definitely a valid expression', - visualizationType: 'testVis', - state: { - query: 'kuery', - filters: [{ query: { match_phrase: { src: 'test' } } }], - datasourceStates: { - testDatasource: 'datasource', - }, - visualization: {}, - }, - references: [{ type: 'index-pattern', id: '1', name: 'index-pattern-0' }], -} as unknown as Document; - -export function createMockTimefilter() { - const unsubscribe = jest.fn(); - - let timeFilter = { from: 'now-7d', to: 'now' }; - let subscriber: () => void; - return { - getTime: jest.fn(() => timeFilter), - setTime: jest.fn((newTimeFilter) => { - timeFilter = newTimeFilter; - if (subscriber) { - subscriber(); - } - }), - getTimeUpdate$: () => ({ - subscribe: ({ next }: { next: () => void }) => { - subscriber = next; - return unsubscribe; - }, - }), - calculateBounds: jest.fn(() => ({ - min: moment('2021-01-10T04:00:00.000Z'), - max: moment('2021-01-10T08:00:00.000Z'), - })), - getBounds: jest.fn(() => timeFilter), - getRefreshInterval: () => {}, - getRefreshIntervalDefaults: () => {}, - getAutoRefreshFetch$: () => new Observable(), - }; -} - -export const exactMatchDoc = { - ...defaultDoc, - sharingSavedObjectProps: { - outcome: 'exactMatch', - }, -}; - -export const mockStoreDeps = (deps?: { - lensServices?: LensAppServices; - datasourceMap?: DatasourceMap; - visualizationMap?: VisualizationMap; -}) => { - return { - datasourceMap: deps?.datasourceMap || datasourceMap, - visualizationMap: deps?.visualizationMap || visualizationMap, - lensServices: deps?.lensServices || makeDefaultServices(), - }; -}; - -export function mockDataPlugin( - sessionIdSubject = new Subject(), - initialSessionId?: string -) { - function createMockSearchService() { - let sessionIdCounter = initialSessionId ? 1 : 0; - let currentSessionId: string | undefined = initialSessionId; - const start = () => { - currentSessionId = `sessionId-${++sessionIdCounter}`; - return currentSessionId; - }; - return { - session: { - start: jest.fn(start), - clear: jest.fn(), - getSessionId: jest.fn(() => currentSessionId), - getSession$: jest.fn(() => sessionIdSubject.asObservable()), - }, - }; - } - - function createMockFilterManager() { - const unsubscribe = jest.fn(); - - let subscriber: () => void; - let filters: unknown = []; - - return { - getUpdates$: () => ({ - subscribe: ({ next }: { next: () => void }) => { - subscriber = next; - return unsubscribe; - }, - }), - setFilters: jest.fn((newFilters: unknown[]) => { - filters = newFilters; - if (subscriber) subscriber(); - }), - setAppFilters: jest.fn((newFilters: unknown[]) => { - filters = newFilters; - if (subscriber) subscriber(); - }), - getFilters: () => filters, - getGlobalFilters: () => { - // @ts-ignore - return filters.filter(esFilters.isFilterPinned); - }, - removeAll: () => { - filters = []; - subscriber(); - }, - }; - } - function createMockQueryString() { - return { - getQuery: jest.fn(() => ({ query: '', language: 'lucene' })), - setQuery: jest.fn(), - getDefaultQuery: jest.fn(() => ({ query: '', language: 'lucene' })), - }; - } - return { - query: { - filterManager: createMockFilterManager(), - timefilter: { - timefilter: createMockTimefilter(), - }, - queryString: createMockQueryString(), - state$: new Observable(), - }, - indexPatterns: { - get: jest.fn().mockImplementation((id) => Promise.resolve({ id, isTimeBased: () => true })), - }, - search: createMockSearchService(), - nowProvider: { - get: jest.fn(), - }, - fieldFormats: { - deserialize: jest.fn(), - }, - } as unknown as DataPublicPluginStart; -} - -export function makeDefaultServices( - sessionIdSubject = new Subject(), - sessionId: string | undefined = undefined, - doc = defaultDoc -): jest.Mocked { - const core = coreMock.createStart({ basePath: '/testbasepath' }); - core.uiSettings.get.mockImplementation( - jest.fn((type) => { - if (type === UI_SETTINGS.TIMEPICKER_TIME_DEFAULTS) { - return { from: 'now-7d', to: 'now' }; - } else if (type === UI_SETTINGS.SEARCH_QUERY_LANGUAGE) { - return 'kuery'; - } else if (type === 'state:storeInSessionStorage') { - return false; - } else { - return []; - } - }) - ); - - const navigationStartMock = navigationPluginMock.createStartContract(); - - jest.spyOn(navigationStartMock.ui.TopNavMenu.prototype, 'constructor').mockImplementation(() => { - return
; - }); - - function makeAttributeService(): LensAttributeService { - const attributeServiceMock = mockAttributeService< - ResolvedLensSavedObjectAttributes, - LensByValueInput, - LensByReferenceInput - >( - DOC_TYPE, - { - saveMethod: jest.fn(), - unwrapMethod: jest.fn(), - checkForDuplicateTitle: jest.fn(), - }, - core - ); - attributeServiceMock.unwrapAttributes = jest.fn().mockResolvedValue(exactMatchDoc); - attributeServiceMock.wrapAttributes = jest.fn().mockResolvedValue({ - savedObjectId: (doc as unknown as LensByReferenceInput).savedObjectId, - }); - - return attributeServiceMock; - } - - return { - http: core.http, - chrome: core.chrome, - overlays: core.overlays, - uiSettings: core.uiSettings, - navigation: navigationStartMock, - notifications: core.notifications, - attributeService: makeAttributeService(), - inspector: { - adapters: getLensInspectorService(inspectorPluginMock.createStartContract()).adapters, - inspect: jest.fn(), - close: jest.fn(), - }, - dashboard: dashboardPluginMock.createStartContract(), - presentationUtil: presentationUtilPluginMock.createStartContract(core), - savedObjectsClient: core.savedObjects.client, - dashboardFeatureFlag: { allowByValueEmbeddables: false }, - stateTransfer: createEmbeddableStateTransferMock() as EmbeddableStateTransfer, - getOriginatingAppName: jest.fn(() => 'defaultOriginatingApp'), - application: { - ...core.application, - capabilities: { - ...core.application.capabilities, - visualize: { save: true, saveQuery: true, show: true }, - }, - getUrlForApp: jest.fn((appId: string) => `/testbasepath/app/${appId}#/`), - }, - data: mockDataPlugin(sessionIdSubject, sessionId), - fieldFormats: fieldFormatsServiceMock.createStartContract(), - storage: { - get: jest.fn(), - set: jest.fn(), - remove: jest.fn(), - clear: jest.fn(), - }, - spaces: spacesPluginMock.createStartContract(), - }; -} - -export const defaultState = { - searchSessionId: 'sessionId-1', - filters: [], - query: { language: 'lucene', query: '' }, - resolvedDateRange: { fromDate: '2021-01-10T04:00:00.000Z', toDate: '2021-01-10T08:00:00.000Z' }, - isFullscreenDatasource: false, - isSaveable: false, - isLoading: false, - isLinkedToOriginatingApp: false, - activeDatasourceId: 'testDatasource', - visualization: { - state: {}, - activeId: 'testVis', - }, - datasourceStates: mockDatasourceStates(), -}; - -export function makeLensStore({ - preloadedState, - dispatch, - storeDeps = mockStoreDeps(), -}: { - storeDeps?: LensStoreDeps; - preloadedState?: Partial; - dispatch?: jest.Mock; -}) { - const data = storeDeps.lensServices.data; - const store = makeConfigureStore(storeDeps, { - lens: { - ...defaultState, - query: data.query.queryString.getQuery(), - filters: data.query.filterManager.getGlobalFilters(), - resolvedDateRange: getResolvedDateRange(data.query.timefilter.timefilter), - ...preloadedState, - }, - } as PreloadedState); - - const origDispatch = store.dispatch; - store.dispatch = jest.fn(dispatch || origDispatch); - return { store, deps: storeDeps }; -} - -export const mountWithProvider = async ( - component: React.ReactElement, - store?: { - storeDeps?: LensStoreDeps; - preloadedState?: Partial; - dispatch?: jest.Mock; - }, - options?: { - wrappingComponent?: React.FC<{ - children: React.ReactNode; - }>; - attachTo?: HTMLElement; - } -) => { - const { store: lensStore, deps } = makeLensStore(store || {}); - - let wrappingComponent: React.FC<{ - children: React.ReactNode; - }> = ({ children }) => {children}; - - let restOptions: { - attachTo?: HTMLElement | undefined; - }; - if (options) { - const { wrappingComponent: _wrappingComponent, ...rest } = options; - restOptions = rest; - - if (_wrappingComponent) { - wrappingComponent = ({ children }) => { - return _wrappingComponent({ - children: {children}, - }); - }; - } - } - - let instance: ReactWrapper = {} as ReactWrapper; - - await act(async () => { - instance = mount(component, { - wrappingComponent, - ...restOptions, - } as unknown as ReactWrapper); - }); - return { instance, lensStore, deps }; -}; diff --git a/x-pack/plugins/lens/public/mocks/data_plugin_mock.ts b/x-pack/plugins/lens/public/mocks/data_plugin_mock.ts new file mode 100644 index 0000000000000..daab2566b28fe --- /dev/null +++ b/x-pack/plugins/lens/public/mocks/data_plugin_mock.ts @@ -0,0 +1,122 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { Observable, Subject } from 'rxjs'; +import moment from 'moment'; +import { DataPublicPluginStart, esFilters } from '../../../../../src/plugins/data/public'; + +function createMockTimefilter() { + const unsubscribe = jest.fn(); + + let timeFilter = { from: 'now-7d', to: 'now' }; + let subscriber: () => void; + return { + getTime: jest.fn(() => timeFilter), + setTime: jest.fn((newTimeFilter) => { + timeFilter = newTimeFilter; + if (subscriber) { + subscriber(); + } + }), + getTimeUpdate$: () => ({ + subscribe: ({ next }: { next: () => void }) => { + subscriber = next; + return unsubscribe; + }, + }), + calculateBounds: jest.fn(() => ({ + min: moment('2021-01-10T04:00:00.000Z'), + max: moment('2021-01-10T08:00:00.000Z'), + })), + getBounds: jest.fn(() => timeFilter), + getRefreshInterval: () => {}, + getRefreshIntervalDefaults: () => {}, + getAutoRefreshFetch$: () => new Observable(), + }; +} + +export function mockDataPlugin( + sessionIdSubject = new Subject(), + initialSessionId?: string +) { + function createMockSearchService() { + let sessionIdCounter = initialSessionId ? 1 : 0; + let currentSessionId: string | undefined = initialSessionId; + const start = () => { + currentSessionId = `sessionId-${++sessionIdCounter}`; + return currentSessionId; + }; + return { + session: { + start: jest.fn(start), + clear: jest.fn(), + getSessionId: jest.fn(() => currentSessionId), + getSession$: jest.fn(() => sessionIdSubject.asObservable()), + }, + }; + } + + function createMockFilterManager() { + const unsubscribe = jest.fn(); + + let subscriber: () => void; + let filters: unknown = []; + + return { + getUpdates$: () => ({ + subscribe: ({ next }: { next: () => void }) => { + subscriber = next; + return unsubscribe; + }, + }), + setFilters: jest.fn((newFilters: unknown[]) => { + filters = newFilters; + if (subscriber) subscriber(); + }), + setAppFilters: jest.fn((newFilters: unknown[]) => { + filters = newFilters; + if (subscriber) subscriber(); + }), + getFilters: () => filters, + getGlobalFilters: () => { + // @ts-ignore + return filters.filter(esFilters.isFilterPinned); + }, + removeAll: () => { + filters = []; + subscriber(); + }, + }; + } + function createMockQueryString() { + return { + getQuery: jest.fn(() => ({ query: '', language: 'lucene' })), + setQuery: jest.fn(), + getDefaultQuery: jest.fn(() => ({ query: '', language: 'lucene' })), + }; + } + return { + query: { + filterManager: createMockFilterManager(), + timefilter: { + timefilter: createMockTimefilter(), + }, + queryString: createMockQueryString(), + state$: new Observable(), + }, + indexPatterns: { + get: jest.fn().mockImplementation((id) => Promise.resolve({ id, isTimeBased: () => true })), + }, + search: createMockSearchService(), + nowProvider: { + get: jest.fn(), + }, + fieldFormats: { + deserialize: jest.fn(), + }, + } as unknown as DataPublicPluginStart; +} diff --git a/x-pack/plugins/lens/public/mocks/datasource_mock.ts b/x-pack/plugins/lens/public/mocks/datasource_mock.ts new file mode 100644 index 0000000000000..2614b1d5fdc94 --- /dev/null +++ b/x-pack/plugins/lens/public/mocks/datasource_mock.ts @@ -0,0 +1,78 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { DatasourcePublicAPI, Datasource } from '../types'; + +export type DatasourceMock = jest.Mocked & { + publicAPIMock: jest.Mocked; +}; + +export function createMockDatasource(id: string): DatasourceMock { + const publicAPIMock: jest.Mocked = { + datasourceId: id, + getTableSpec: jest.fn(() => []), + getOperationForColumnId: jest.fn(), + }; + + return { + id: 'testDatasource', + clearLayer: jest.fn((state, _layerId) => state), + getDatasourceSuggestionsForField: jest.fn((_state, _item, filterFn) => []), + getDatasourceSuggestionsForVisualizeField: jest.fn((_state, _indexpatternId, _fieldName) => []), + getDatasourceSuggestionsFromCurrentState: jest.fn((_state) => []), + getPersistableState: jest.fn((x) => ({ + state: x, + savedObjectReferences: [{ type: 'index-pattern', id: 'mockip', name: 'mockip' }], + })), + getPublicAPI: jest.fn().mockReturnValue(publicAPIMock), + initialize: jest.fn((_state?) => Promise.resolve()), + renderDataPanel: jest.fn(), + renderLayerPanel: jest.fn(), + toExpression: jest.fn((_frame, _state) => null), + insertLayer: jest.fn((_state, _newLayerId) => ({})), + removeLayer: jest.fn((_state, _layerId) => {}), + removeColumn: jest.fn((props) => {}), + getLayers: jest.fn((_state) => []), + uniqueLabels: jest.fn((_state) => ({})), + renderDimensionTrigger: jest.fn(), + renderDimensionEditor: jest.fn(), + getDropProps: jest.fn(), + onDrop: jest.fn(), + + // this is an additional property which doesn't exist on real datasources + // but can be used to validate whether specific API mock functions are called + publicAPIMock, + getErrorMessages: jest.fn((_state) => undefined), + checkIntegrity: jest.fn((_state) => []), + isTimeBased: jest.fn(), + isValidColumn: jest.fn(), + }; +} + +export function mockDatasourceMap() { + const datasource = createMockDatasource('testDatasource'); + datasource.getDatasourceSuggestionsFromCurrentState.mockReturnValue([ + { + state: {}, + table: { + columns: [], + isMultiRow: true, + layerId: 'a', + changeType: 'unchanged', + }, + keptLayerIds: ['a'], + }, + ]); + + datasource.getLayers.mockReturnValue(['a']); + return { + testDatasource2: createMockDatasource('testDatasource2'), + testDatasource: datasource, + }; +} + +export const datasourceMap = mockDatasourceMap(); diff --git a/x-pack/plugins/lens/public/mocks/expression_renderer_mock.tsx b/x-pack/plugins/lens/public/mocks/expression_renderer_mock.tsx new file mode 100644 index 0000000000000..644021e8a69c2 --- /dev/null +++ b/x-pack/plugins/lens/public/mocks/expression_renderer_mock.tsx @@ -0,0 +1,16 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { ReactExpressionRendererProps } from 'src/plugins/expressions/public'; + +export function createExpressionRendererMock(): jest.Mock< + React.ReactElement, + [ReactExpressionRendererProps] +> { + return jest.fn((_) => ); +} diff --git a/x-pack/plugins/lens/public/mocks/index.ts b/x-pack/plugins/lens/public/mocks/index.ts new file mode 100644 index 0000000000000..2dd32a1679f1b --- /dev/null +++ b/x-pack/plugins/lens/public/mocks/index.ts @@ -0,0 +1,42 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FramePublicAPI, FrameDatasourceAPI } from '../types'; +export { mockDataPlugin } from './data_plugin_mock'; +export { + visualizationMap, + createMockVisualization, + mockVisualizationMap, +} from './visualization_mock'; +export { datasourceMap, mockDatasourceMap, createMockDatasource } from './datasource_mock'; +export type { DatasourceMock } from './datasource_mock'; +export { createExpressionRendererMock } from './expression_renderer_mock'; +export { defaultDoc, exactMatchDoc, makeDefaultServices } from './services_mock'; +export { + mockStoreDeps, + mockDatasourceStates, + defaultState, + makeLensStore, + MountStoreProps, + mountWithProvider, +} from './store_mocks'; +export { lensPluginMock } from './lens_plugin_mock'; + +export type FrameMock = jest.Mocked; + +export const createMockFramePublicAPI = (): FrameMock => ({ + datasourceLayers: {}, +}); + +export type FrameDatasourceMock = jest.Mocked; + +export const createMockFrameDatasourceAPI = (): FrameDatasourceMock => ({ + datasourceLayers: {}, + dateRange: { fromDate: 'now-7d', toDate: 'now' }, + query: { query: '', language: 'lucene' }, + filters: [], +}); diff --git a/x-pack/plugins/lens/public/mocks/lens_plugin_mock.tsx b/x-pack/plugins/lens/public/mocks/lens_plugin_mock.tsx new file mode 100644 index 0000000000000..a92533a89ba67 --- /dev/null +++ b/x-pack/plugins/lens/public/mocks/lens_plugin_mock.tsx @@ -0,0 +1,31 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { LensPublicStart } from '..'; +import { visualizationTypes } from '../xy_visualization/types'; + +type Start = jest.Mocked; + +export const lensPluginMock = { + createStartContract: (): Start => { + const startContract: Start = { + EmbeddableComponent: jest.fn(() => { + return Lens Embeddable Component; + }), + SaveModalComponent: jest.fn(() => { + return Lens Save Modal Component; + }), + canUseEditor: jest.fn(() => true), + navigateToPrefilledEditor: jest.fn(), + getXyVisTypes: jest + .fn() + .mockReturnValue(new Promise((resolve) => resolve(visualizationTypes))), + }; + return startContract; + }, +}; diff --git a/x-pack/plugins/lens/public/mocks/services_mock.tsx b/x-pack/plugins/lens/public/mocks/services_mock.tsx new file mode 100644 index 0000000000000..c6db0dfb6aae8 --- /dev/null +++ b/x-pack/plugins/lens/public/mocks/services_mock.tsx @@ -0,0 +1,143 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { Subject } from 'rxjs'; +import { coreMock } from 'src/core/public/mocks'; +import { navigationPluginMock } from '../../../../../src/plugins/navigation/public/mocks'; +import { LensAppServices } from '../app_plugin/types'; +import { DOC_TYPE } from '../../common'; +import { UI_SETTINGS } from '../../../../../src/plugins/data/public'; +import { inspectorPluginMock } from '../../../../../src/plugins/inspector/public/mocks'; +import { spacesPluginMock } from '../../../spaces/public/mocks'; +import { dashboardPluginMock } from '../../../../../src/plugins/dashboard/public/mocks'; +import type { + LensByValueInput, + LensByReferenceInput, + ResolvedLensSavedObjectAttributes, +} from '../embeddable/embeddable'; +import { + mockAttributeService, + createEmbeddableStateTransferMock, +} from '../../../../../src/plugins/embeddable/public/mocks'; +import { fieldFormatsServiceMock } from '../../../../../src/plugins/field_formats/public/mocks'; +import type { LensAttributeService } from '../lens_attribute_service'; +import type { EmbeddableStateTransfer } from '../../../../../src/plugins/embeddable/public'; + +import { presentationUtilPluginMock } from '../../../../../src/plugins/presentation_util/public/mocks'; +import { mockDataPlugin } from './data_plugin_mock'; +import { getLensInspectorService } from '../lens_inspector_service'; + +export const defaultDoc = { + savedObjectId: '1234', + title: 'An extremely cool default document!', + expression: 'definitely a valid expression', + visualizationType: 'testVis', + state: { + query: 'kuery', + filters: [{ query: { match_phrase: { src: 'test' } } }], + datasourceStates: { + testDatasource: 'datasource', + }, + visualization: {}, + }, + references: [{ type: 'index-pattern', id: '1', name: 'index-pattern-0' }], +} as unknown as Document; + +export const exactMatchDoc = { + ...defaultDoc, + sharingSavedObjectProps: { + outcome: 'exactMatch', + }, +}; + +export function makeDefaultServices( + sessionIdSubject = new Subject(), + sessionId: string | undefined = undefined, + doc = defaultDoc +): jest.Mocked { + const core = coreMock.createStart({ basePath: '/testbasepath' }); + core.uiSettings.get.mockImplementation( + jest.fn((type) => { + if (type === UI_SETTINGS.TIMEPICKER_TIME_DEFAULTS) { + return { from: 'now-7d', to: 'now' }; + } else if (type === UI_SETTINGS.SEARCH_QUERY_LANGUAGE) { + return 'kuery'; + } else if (type === 'state:storeInSessionStorage') { + return false; + } else { + return []; + } + }) + ); + + const navigationStartMock = navigationPluginMock.createStartContract(); + + jest.spyOn(navigationStartMock.ui.TopNavMenu.prototype, 'constructor').mockImplementation(() => { + return
; + }); + + function makeAttributeService(): LensAttributeService { + const attributeServiceMock = mockAttributeService< + ResolvedLensSavedObjectAttributes, + LensByValueInput, + LensByReferenceInput + >( + DOC_TYPE, + { + saveMethod: jest.fn(), + unwrapMethod: jest.fn(), + checkForDuplicateTitle: jest.fn(), + }, + core + ); + attributeServiceMock.unwrapAttributes = jest.fn().mockResolvedValue(exactMatchDoc); + attributeServiceMock.wrapAttributes = jest.fn().mockResolvedValue({ + savedObjectId: (doc as unknown as LensByReferenceInput).savedObjectId, + }); + + return attributeServiceMock; + } + + return { + http: core.http, + chrome: core.chrome, + overlays: core.overlays, + uiSettings: core.uiSettings, + navigation: navigationStartMock, + notifications: core.notifications, + attributeService: makeAttributeService(), + inspector: { + adapters: getLensInspectorService(inspectorPluginMock.createStartContract()).adapters, + inspect: jest.fn(), + close: jest.fn(), + }, + dashboard: dashboardPluginMock.createStartContract(), + presentationUtil: presentationUtilPluginMock.createStartContract(core), + savedObjectsClient: core.savedObjects.client, + dashboardFeatureFlag: { allowByValueEmbeddables: false }, + stateTransfer: createEmbeddableStateTransferMock() as EmbeddableStateTransfer, + getOriginatingAppName: jest.fn(() => 'defaultOriginatingApp'), + application: { + ...core.application, + capabilities: { + ...core.application.capabilities, + visualize: { save: true, saveQuery: true, show: true }, + }, + getUrlForApp: jest.fn((appId: string) => `/testbasepath/app/${appId}#/`), + }, + data: mockDataPlugin(sessionIdSubject, sessionId), + fieldFormats: fieldFormatsServiceMock.createStartContract(), + storage: { + get: jest.fn(), + set: jest.fn(), + remove: jest.fn(), + clear: jest.fn(), + }, + spaces: spacesPluginMock.createStartContract(), + }; +} diff --git a/x-pack/plugins/lens/public/mocks/store_mocks.tsx b/x-pack/plugins/lens/public/mocks/store_mocks.tsx new file mode 100644 index 0000000000000..1b1d83ef2892d --- /dev/null +++ b/x-pack/plugins/lens/public/mocks/store_mocks.tsx @@ -0,0 +1,140 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +// eslint-disable-next-line import/no-extraneous-dependencies +import { ReactWrapper } from 'enzyme'; +// eslint-disable-next-line import/no-extraneous-dependencies +import { mountWithIntl as mount } from '@kbn/test/jest'; +import { Provider } from 'react-redux'; +import { act } from 'react-dom/test-utils'; +import { PreloadedState } from '@reduxjs/toolkit'; +import { LensAppServices } from '../app_plugin/types'; + +import { + makeConfigureStore, + LensAppState, + LensState, + LensStoreDeps, +} from '../state_management/index'; +import { getResolvedDateRange } from '../utils'; +import { DatasourceMap, VisualizationMap } from '../types'; +import { mockVisualizationMap } from './visualization_mock'; +import { mockDatasourceMap } from './datasource_mock'; +import { makeDefaultServices } from './services_mock'; + +export const mockStoreDeps = (deps?: { + lensServices?: LensAppServices; + datasourceMap?: DatasourceMap; + visualizationMap?: VisualizationMap; +}) => { + return { + datasourceMap: deps?.datasourceMap || mockDatasourceMap(), + visualizationMap: deps?.visualizationMap || mockVisualizationMap(), + lensServices: deps?.lensServices || makeDefaultServices(), + }; +}; + +export function mockDatasourceStates() { + return { + testDatasource: { + state: {}, + isLoading: false, + }, + }; +} + +export const defaultState = { + searchSessionId: 'sessionId-1', + filters: [], + query: { language: 'lucene', query: '' }, + resolvedDateRange: { fromDate: '2021-01-10T04:00:00.000Z', toDate: '2021-01-10T08:00:00.000Z' }, + isFullscreenDatasource: false, + isSaveable: false, + isLoading: false, + isLinkedToOriginatingApp: false, + activeDatasourceId: 'testDatasource', + visualization: { + state: {}, + activeId: 'testVis', + }, + datasourceStates: mockDatasourceStates(), +}; + +export function makeLensStore({ + preloadedState, + dispatch, + storeDeps = mockStoreDeps(), +}: { + storeDeps?: LensStoreDeps; + preloadedState?: Partial; + dispatch?: jest.Mock; +}) { + const data = storeDeps.lensServices.data; + const store = makeConfigureStore(storeDeps, { + lens: { + ...defaultState, + query: data.query.queryString.getQuery(), + filters: data.query.filterManager.getGlobalFilters(), + resolvedDateRange: getResolvedDateRange(data.query.timefilter.timefilter), + ...preloadedState, + }, + } as PreloadedState); + + const origDispatch = store.dispatch; + store.dispatch = jest.fn(dispatch || origDispatch); + return { store, deps: storeDeps }; +} + +export interface MountStoreProps { + storeDeps?: LensStoreDeps; + preloadedState?: Partial; + dispatch?: jest.Mock; +} + +export const mountWithProvider = async ( + component: React.ReactElement, + store?: MountStoreProps, + options?: { + wrappingComponent?: React.FC<{ + children: React.ReactNode; + }>; + attachTo?: HTMLElement; + } +) => { + const { store: lensStore, deps } = makeLensStore(store || {}); + + let wrappingComponent: React.FC<{ + children: React.ReactNode; + }> = ({ children }) => {children}; + + let restOptions: { + attachTo?: HTMLElement | undefined; + }; + if (options) { + const { wrappingComponent: _wrappingComponent, ...rest } = options; + restOptions = rest; + + if (_wrappingComponent) { + wrappingComponent = ({ children }) => { + return _wrappingComponent({ + children: {children}, + }); + }; + } + } + + let instance: ReactWrapper = {} as ReactWrapper; + + await act(async () => { + instance = mount(component, { + wrappingComponent, + ...restOptions, + } as unknown as ReactWrapper); + }); + return { instance, lensStore, deps }; +}; diff --git a/x-pack/plugins/lens/public/mocks/visualization_mock.ts b/x-pack/plugins/lens/public/mocks/visualization_mock.ts new file mode 100644 index 0000000000000..199bf9a9db77a --- /dev/null +++ b/x-pack/plugins/lens/public/mocks/visualization_mock.ts @@ -0,0 +1,63 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { layerTypes } from '../../common'; +import { Visualization, VisualizationMap } from '../types'; + +export function createMockVisualization(id = 'testVis'): jest.Mocked { + return { + id, + clearLayer: jest.fn((state, _layerId) => state), + removeLayer: jest.fn(), + getLayerIds: jest.fn((_state) => ['layer1']), + getSupportedLayers: jest.fn(() => [{ type: layerTypes.DATA, label: 'Data Layer' }]), + getLayerType: jest.fn((_state, _layerId) => layerTypes.DATA), + visualizationTypes: [ + { + icon: 'empty', + id, + label: 'TEST', + groupLabel: `${id}Group`, + }, + ], + appendLayer: jest.fn(), + getVisualizationTypeId: jest.fn((_state) => 'empty'), + getDescription: jest.fn((_state) => ({ label: '' })), + switchVisualizationType: jest.fn((_, x) => x), + getSuggestions: jest.fn((_options) => []), + initialize: jest.fn((_frame, _state?) => ({ newState: 'newState' })), + getConfiguration: jest.fn((props) => ({ + groups: [ + { + groupId: 'a', + groupLabel: 'a', + layerId: 'layer1', + supportsMoreColumns: true, + accessors: [], + filterOperations: jest.fn(() => true), + dataTestSubj: 'mockVisA', + }, + ], + })), + toExpression: jest.fn((_state, _frame) => null), + toPreviewExpression: jest.fn((_state, _frame) => null), + + setDimension: jest.fn(), + removeDimension: jest.fn(), + getErrorMessages: jest.fn((_state) => undefined), + renderDimensionEditor: jest.fn(), + }; +} + +export const mockVisualizationMap = (): VisualizationMap => { + return { + testVis: createMockVisualization(), + testVis2: createMockVisualization(), + }; +}; + +export const visualizationMap = mockVisualizationMap();