diff --git a/src/platform/packages/shared/kbn-resizable-layout/src/panels_resizable.test.tsx b/src/platform/packages/shared/kbn-resizable-layout/src/panels_resizable.test.tsx index 4057b1331af5e..1f5fdda6fae6b 100644 --- a/src/platform/packages/shared/kbn-resizable-layout/src/panels_resizable.test.tsx +++ b/src/platform/packages/shared/kbn-resizable-layout/src/panels_resizable.test.tsx @@ -7,23 +7,47 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { type ReactWrapper, mount } from 'enzyme'; -import { act } from 'react-dom/test-utils'; -import { waitFor } from '@testing-library/react'; import type { ReactElement } from 'react'; import React from 'react'; +import { act, render, screen, waitFor } from '@testing-library/react'; import { PanelsResizable } from './panels_resizable'; -import { ResizableLayoutOrder } from '../types'; import { ResizableLayoutDirection } from '../types'; +import { ResizableLayoutOrder } from '../types'; const containerHeight = 1000; + const containerWidth = 500; + const fixedPanelId = 'fixedPanel'; -jest.mock('@elastic/eui', () => ({ - ...jest.requireActual('@elastic/eui'), - useGeneratedHtmlId: jest.fn(() => fixedPanelId), -})); +interface ResizableContainerCallbacks { + direction?: ResizableLayoutDirection; + onPanelWidthChange?: (sizes: Record) => void; + onResizeEnd?: () => void; + onResizeStart?: (trigger: string) => void; +} + +let resizableContainerCallbacks: ResizableContainerCallbacks = {}; + +jest.mock('@elastic/eui', () => { + const actual = jest.requireActual('@elastic/eui'); + const ActualEuiResizableContainer = actual.EuiResizableContainer; + + return { + ...actual, + useGeneratedHtmlId: jest.fn(() => fixedPanelId), + EuiResizableContainer: (props: React.ComponentProps) => { + resizableContainerCallbacks = { + direction: props.direction, + onPanelWidthChange: props.onPanelWidthChange, + onResizeEnd: props.onResizeEnd, + onResizeStart: props.onResizeStart, + }; + + return ; + }, + }; +}); let resizeObserverCallback: (height: number, width: number) => void = jest.fn(); @@ -42,66 +66,96 @@ window.ResizeObserver = class ResizeObserver { }; describe('Panels resizable', () => { - const mountComponent = ({ + const getPanelSizePct = (testSubj: string, direction: ResizableLayoutDirection) => { + const panelWrapper = screen.getByTestId(testSubj).closest('.euiResizablePanel'); + + if (!panelWrapper) throw new Error(`Could not find resizable panel wrapper for ${testSubj}`); + + const { style } = panelWrapper as HTMLElement; + + const sizeValue = + direction === ResizableLayoutDirection.Horizontal + ? style.inlineSize || style.width + : style.blockSize || style.height; + + return parseFloat(sizeValue); + }; + + const expectCorrectPanelSizes = ( + direction: ResizableLayoutDirection, + currentContainerSize: number, + fixedPanelSize: number + ) => { + const fixedPanelSizePct = (fixedPanelSize / currentContainerSize) * 100; + + expect(getPanelSizePct('resizableLayoutResizablePanelFixed', direction)).toBe( + fixedPanelSizePct + ); + expect(getPanelSizePct('resizableLayoutResizablePanelFlex', direction)).toBe( + 100 - fixedPanelSizePct + ); + }; + + const renderPanelsResizable = ( + props: React.ComponentProps, + options?: { container?: HTMLElement } + ) => + render( + , + options?.container ? { container: options.container } : undefined + ); + + const renderComponent = ({ className = '', + container, direction = ResizableLayoutDirection.Vertical, + fixedPanel =
Fixed panel
, + fixedPanelOrder, + flexPanel =
Flex panel
, initialFixedPanelSize = 0, minFixedPanelSize = 0, minFlexPanelSize = 0, - fixedPanel = <>, - flexPanel = <>, - fixedPanelOrder, - attachTo, onFixedPanelSizeChange = jest.fn(), }: { className?: string; + container?: HTMLElement; direction?: ResizableLayoutDirection; + fixedPanel?: ReactElement; + fixedPanelOrder?: ResizableLayoutOrder; + flexPanel?: ReactElement; initialFixedPanelSize?: number | 'max-content'; minFixedPanelSize?: number; minFlexPanelSize?: number; - fixedPanel?: ReactElement; - flexPanel?: ReactElement; - fixedPanelOrder?: ResizableLayoutOrder; - attachTo?: HTMLElement; onFixedPanelSizeChange?: (fixedPanelSize: number) => void; - }) => { - const PanelsWrapper = ({ fixedPanelSize }: { fixedPanelSize?: number }) => ( - + } = {}) => { + return renderPanelsResizable( + { + className, + direction, + fixedPanel, + fixedPanelOrder, + fixedPanelSize: initialFixedPanelSize, + flexPanel, + minFixedPanelSize, + minFlexPanelSize, + onFixedPanelSizeChange, + }, + { container } ); - - return mount(, attachTo ? { attachTo } : undefined); }; - const expectCorrectPanelSizes = ( - component: ReactWrapper, - currentContainerSize: number, - fixedPanelSize: number - ) => { - const fixedPanelSizePct = (fixedPanelSize / currentContainerSize) * 100; - expect( - component.find('[data-test-subj="resizableLayoutResizablePanelFixed"]').at(0).prop('size') - ).toBe(fixedPanelSizePct); - expect( - component.find('[data-test-subj="resizableLayoutResizablePanelFlex"]').at(0).prop('size') - ).toBe(100 - fixedPanelSizePct); - }; + const expectPanelOrder = (firstPanelText: string, secondPanelText: string) => { + const firstPanel = screen.getByText(firstPanelText); + const secondPanel = screen.getByText(secondPanelText); - const forceRender = (component: ReactWrapper) => { - component.setProps({}).update(); + expect(firstPanel.compareDocumentPosition(secondPanel)).toBe(Node.DOCUMENT_POSITION_FOLLOWING); }; beforeEach(() => { + resizableContainerCallbacks = {}; + resizeObserverCallback = jest.fn(); + window.HTMLElement.prototype.getBoundingClientRect = jest.fn(() => { return { height: containerHeight, @@ -111,152 +165,256 @@ describe('Panels resizable', () => { }); it('should render both panels', () => { - const fixedPanel =
; - const flexPanel =
; - const component = mountComponent({ fixedPanel, flexPanel }); - expect(component.contains(fixedPanel)).toBe(true); - expect(component.contains(flexPanel)).toBe(true); + renderComponent(); + + expect(screen.getByText('Fixed panel')).toBeVisible(); + expect(screen.getByText('Flex panel')).toBeVisible(); }); it('should set the initial sizes of both panels', () => { const initialFixedPanelSize = 200; - const component = mountComponent({ initialFixedPanelSize }); - expectCorrectPanelSizes(component, containerHeight, initialFixedPanelSize); + + renderComponent({ initialFixedPanelSize }); + + expectCorrectPanelSizes( + ResizableLayoutDirection.Vertical, + containerHeight, + initialFixedPanelSize + ); }); it('should set the correct sizes of both panels when the panels are resized', () => { const initialFixedPanelSize = 200; - const onFixedPanelSizeChange = jest.fn((fixedPanelSize) => { - component.setProps({ fixedPanelSize }).update(); - }); - const component = mountComponent({ initialFixedPanelSize, onFixedPanelSizeChange }); - expectCorrectPanelSizes(component, containerHeight, initialFixedPanelSize); + let fixedPanelSize: number | 'max-content' = initialFixedPanelSize; + const onFixedPanelSizeChange = jest.fn(); + + let rerenderPanels: ReturnType['rerender'] = () => undefined; + + const handleFixedPanelSizeChange = (size: number) => { + onFixedPanelSizeChange(size); + fixedPanelSize = size; + rerenderPanels( + } + fixedPanelSize={fixedPanelSize} + flexPanel={<>} + minFixedPanelSize={0} + minFlexPanelSize={0} + onFixedPanelSizeChange={handleFixedPanelSizeChange} + /> + ); + }; + + ({ rerender: rerenderPanels } = renderPanelsResizable({ + direction: ResizableLayoutDirection.Vertical, + fixedPanel: <>, + fixedPanelSize, + flexPanel: <>, + minFixedPanelSize: 0, + minFlexPanelSize: 0, + onFixedPanelSizeChange: handleFixedPanelSizeChange, + })); + + expectCorrectPanelSizes( + ResizableLayoutDirection.Vertical, + containerHeight, + initialFixedPanelSize + ); + const newFixedPanelSizePct = 30; - const onPanelSizeChange = component - .find('[data-test-subj="resizableLayoutResizableContainer"]') - .at(0) - .prop('onPanelWidthChange') as Function; + act(() => { - onPanelSizeChange({ [fixedPanelId]: newFixedPanelSizePct }); + resizableContainerCallbacks.onPanelWidthChange?.({ [fixedPanelId]: newFixedPanelSizePct }); }); - forceRender(component); + const newFixedPanelSize = (newFixedPanelSizePct / 100) * containerHeight; + expect(onFixedPanelSizeChange).toHaveBeenCalledWith(newFixedPanelSize); - expectCorrectPanelSizes(component, containerHeight, newFixedPanelSize); + expectCorrectPanelSizes(ResizableLayoutDirection.Vertical, containerHeight, newFixedPanelSize); }); it('should maintain the size of the fixed panel and resize the flex panel when the container size changes', () => { const initialFixedPanelSize = 200; - const component = mountComponent({ initialFixedPanelSize }); - expectCorrectPanelSizes(component, containerHeight, initialFixedPanelSize); + + renderComponent({ initialFixedPanelSize }); + + expectCorrectPanelSizes( + ResizableLayoutDirection.Vertical, + containerHeight, + initialFixedPanelSize + ); + const newContainerSize = 2000; - resizeObserverCallback(newContainerSize, 0); - forceRender(component); - expectCorrectPanelSizes(component, newContainerSize, initialFixedPanelSize); + + act(() => { + resizeObserverCallback(newContainerSize, 0); + }); + + expectCorrectPanelSizes( + ResizableLayoutDirection.Vertical, + newContainerSize, + initialFixedPanelSize + ); }); it('should resize the fixed panel once the flex panel is at its minimum size', () => { const initialFixedPanelSize = 500; const minFixedPanelSize = 100; const minFlexPanelSize = 100; - const component = mountComponent({ + + renderComponent({ initialFixedPanelSize, minFixedPanelSize, minFlexPanelSize, }); - expectCorrectPanelSizes(component, containerHeight, initialFixedPanelSize); + + expectCorrectPanelSizes( + ResizableLayoutDirection.Vertical, + containerHeight, + initialFixedPanelSize + ); + const newContainerSize = 400; - resizeObserverCallback(newContainerSize, 0); - forceRender(component); - expectCorrectPanelSizes(component, newContainerSize, newContainerSize - minFlexPanelSize); - resizeObserverCallback(containerHeight, 0); - forceRender(component); - expectCorrectPanelSizes(component, containerHeight, initialFixedPanelSize); + + act(() => { + resizeObserverCallback(newContainerSize, 0); + }); + + expectCorrectPanelSizes( + ResizableLayoutDirection.Vertical, + newContainerSize, + newContainerSize - minFlexPanelSize + ); + + act(() => { + resizeObserverCallback(containerHeight, 0); + }); + + expectCorrectPanelSizes( + ResizableLayoutDirection.Vertical, + containerHeight, + initialFixedPanelSize + ); }); it('should maintain the minimum sizes of both panels when the container is too small to fit them', () => { const initialFixedPanelSize = 500; const minFixedPanelSize = 100; const minFlexPanelSize = 150; - const component = mountComponent({ + + renderComponent({ initialFixedPanelSize, minFixedPanelSize, minFlexPanelSize, }); - expectCorrectPanelSizes(component, containerHeight, initialFixedPanelSize); + + expectCorrectPanelSizes( + ResizableLayoutDirection.Vertical, + containerHeight, + initialFixedPanelSize + ); + const newContainerSize = 200; - resizeObserverCallback(newContainerSize, 0); - forceRender(component); + + act(() => { + resizeObserverCallback(newContainerSize, 0); + }); + expect( - component.find('[data-test-subj="resizableLayoutResizablePanelFixed"]').at(0).prop('size') + getPanelSizePct('resizableLayoutResizablePanelFixed', ResizableLayoutDirection.Vertical) ).toBe((minFixedPanelSize / newContainerSize) * 100); + expect( - component.find('[data-test-subj="resizableLayoutResizablePanelFlex"]').at(0).prop('size') + getPanelSizePct('resizableLayoutResizablePanelFlex', ResizableLayoutDirection.Vertical) ).toBe((minFlexPanelSize / newContainerSize) * 100); - resizeObserverCallback(containerHeight, 0); - forceRender(component); - expectCorrectPanelSizes(component, containerHeight, initialFixedPanelSize); + + act(() => { + resizeObserverCallback(containerHeight, 0); + }); + + expectCorrectPanelSizes( + ResizableLayoutDirection.Vertical, + containerHeight, + initialFixedPanelSize + ); }); it('should blur the resize button after a resize', async () => { const attachTo = document.createElement('div'); document.body.appendChild(attachTo); - const component = mountComponent({ attachTo }); - const getContainer = () => - component.find('[data-test-subj="resizableLayoutResizableContainer"]').at(0); - const resizeButton = component.find('button[data-test-subj="resizableLayoutResizableButton"]'); + + renderComponent({ container: attachTo }); + + const resizeButton = screen.getByTestId('resizableLayoutResizableButton'); + act(() => { - const onResizeStart = getContainer().prop('onResizeStart') as Function; - onResizeStart('pointer'); + resizableContainerCallbacks.onResizeStart?.('pointer'); }); - (resizeButton.getDOMNode() as HTMLElement).focus(); - forceRender(component); + act(() => { - const onResizeEnd = getContainer().prop('onResizeEnd') as Function; - onResizeEnd(); + resizeButton.focus(); }); - expect(resizeButton.getDOMNode()).toHaveFocus(); + + act(() => { + resizableContainerCallbacks.onResizeEnd?.(); + }); + + expect(resizeButton).toHaveFocus(); + await waitFor(() => { - expect(resizeButton.getDOMNode()).not.toHaveFocus(); + expect(resizeButton).not.toHaveFocus(); }); + + document.body.removeChild(attachTo); }); it('should pass direction "vertical" to EuiResizableContainer when direction is ResizableLayoutDirection.Vertical', () => { - const component = mountComponent({ direction: ResizableLayoutDirection.Vertical }); - expect( - component.find('[data-test-subj="resizableLayoutResizableContainer"]').at(0).prop('direction') - ).toBe('vertical'); + renderComponent({ direction: ResizableLayoutDirection.Vertical }); + + expect(resizableContainerCallbacks.direction).toBe('vertical'); }); it('should pass direction "horizontal" to EuiResizableContainer when direction is ResizableLayoutDirection.Horizontal', () => { - const component = mountComponent({ direction: ResizableLayoutDirection.Horizontal }); - expect( - component.find('[data-test-subj="resizableLayoutResizableContainer"]').at(0).prop('direction') - ).toBe('horizontal'); + renderComponent({ direction: ResizableLayoutDirection.Horizontal }); + + expect(resizableContainerCallbacks.direction).toBe('horizontal'); }); it('should use containerHeight when direction is ResizableLayoutDirection.Vertical', () => { const initialFixedPanelSize = 200; - const component = mountComponent({ + + renderComponent({ direction: ResizableLayoutDirection.Vertical, initialFixedPanelSize, }); - expectCorrectPanelSizes(component, containerHeight, initialFixedPanelSize); + + expectCorrectPanelSizes( + ResizableLayoutDirection.Vertical, + containerHeight, + initialFixedPanelSize + ); }); it('should use containerWidth when direction is ResizableLayoutDirection.Horizontal', () => { const initialFixedPanelSize = 200; - const component = mountComponent({ + + renderComponent({ direction: ResizableLayoutDirection.Horizontal, initialFixedPanelSize, }); - expectCorrectPanelSizes(component, containerWidth, initialFixedPanelSize); + + expectCorrectPanelSizes( + ResizableLayoutDirection.Horizontal, + containerWidth, + initialFixedPanelSize + ); }); it('should set the initial fixed panel size to max-content', () => { const minFlexPanelSize = 200; - const component = mountComponent({ + renderComponent({ minFlexPanelSize, initialFixedPanelSize: 'max-content', }); @@ -264,49 +422,29 @@ describe('Panels resizable', () => { const flexPanelSizePct = (minFlexPanelSize / containerHeight) * 100; expect( - component.find('[data-test-subj="resizableLayoutResizablePanelFixed"]').at(0).prop('size') + getPanelSizePct('resizableLayoutResizablePanelFixed', ResizableLayoutDirection.Vertical) ).toBe(100 - flexPanelSizePct); expect( - component.find('[data-test-subj="resizableLayoutResizablePanelFlex"]').at(0).prop('size') + getPanelSizePct('resizableLayoutResizablePanelFlex', ResizableLayoutDirection.Vertical) ).toBe(flexPanelSizePct); }); it('should render the panels in the correct order when no fixedPanelOrder is set', () => { - const component = mountComponent({ - fixedPanel:
, - flexPanel:
, - }); + renderComponent(); - const panels = component.find('.testPanelOrder'); - - expect(panels.at(0).prop('data-test-subj')).toBe('testFixedPanel'); - expect(panels.at(1).prop('data-test-subj')).toBe('testFlexPanel'); + expectPanelOrder('Fixed panel', 'Flex panel'); }); it('should render the panels in the correct order when fixedPanelOrder is start', () => { - const component = mountComponent({ - fixedPanelOrder: ResizableLayoutOrder.Start, - fixedPanel:
, - flexPanel:
, - }); + renderComponent({ fixedPanelOrder: ResizableLayoutOrder.Start }); - const panels = component.find('.testPanelOrder'); - - expect(panels.at(0).prop('data-test-subj')).toBe('testFixedPanel'); - expect(panels.at(1).prop('data-test-subj')).toBe('testFlexPanel'); + expectPanelOrder('Fixed panel', 'Flex panel'); }); it('should render the panels in the correct order when fixedPanelOrder is end', () => { - const component = mountComponent({ - fixedPanelOrder: ResizableLayoutOrder.End, - fixedPanel:
, - flexPanel:
, - }); - - const panels = component.find('.testPanelOrder'); + renderComponent({ fixedPanelOrder: ResizableLayoutOrder.End }); - expect(panels.at(0).prop('data-test-subj')).toBe('testFlexPanel'); - expect(panels.at(1).prop('data-test-subj')).toBe('testFixedPanel'); + expectPanelOrder('Flex panel', 'Fixed panel'); }); }); diff --git a/src/platform/packages/shared/kbn-resizable-layout/src/panels_static.test.tsx b/src/platform/packages/shared/kbn-resizable-layout/src/panels_static.test.tsx index 4497ced9b55ad..8ec787f79cd3d 100644 --- a/src/platform/packages/shared/kbn-resizable-layout/src/panels_static.test.tsx +++ b/src/platform/packages/shared/kbn-resizable-layout/src/panels_static.test.tsx @@ -7,66 +7,61 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { EuiFlexGroup } from '@elastic/eui'; -import { mount } from 'enzyme'; import type { ReactElement } from 'react'; import React from 'react'; -import { ResizableLayoutDirection } from '../types'; import { PanelsStatic } from './panels_static'; +import { render, screen } from '@testing-library/react'; +import { ResizableLayoutDirection } from '../types'; describe('Panels static', () => { - const mountComponent = ({ + const renderComponent = ({ direction = ResizableLayoutDirection.Vertical, + fixedPanel =
Fixed panel
, + flexPanel =
Flex panel
, hideFixedPanel = false, - fixedPanel = <>, - flexPanel = <>, }: { direction?: ResizableLayoutDirection; + fixedPanel?: ReactElement; + flexPanel?: ReactElement; hideFixedPanel?: boolean; - fixedPanel: ReactElement; - flexPanel: ReactElement; - }) => { - return mount( + } = {}) => { + return render( ); }; it('should render both panels when hideFixedPanel is false', () => { - const fixedPanel =
; - const flexPanel =
; - const component = mountComponent({ fixedPanel, flexPanel }); - expect(component.contains(fixedPanel)).toBe(true); - expect(component.contains(flexPanel)).toBe(true); + renderComponent(); + + expect(screen.getByText('Fixed panel')).toBeVisible(); + expect(screen.getByText('Flex panel')).toBeVisible(); }); it('should render only flex panel when hideFixedPanel is true', () => { - const fixedPanel =
; - const flexPanel =
; - const component = mountComponent({ hideFixedPanel: true, fixedPanel, flexPanel }); - expect(component.contains(fixedPanel)).toBe(false); - expect(component.contains(flexPanel)).toBe(true); + renderComponent({ hideFixedPanel: true }); + + expect(screen.queryByText('Fixed panel')).not.toBeInTheDocument(); + expect(screen.getByText('Flex panel')).toBeVisible(); }); it('should pass direction "column" to EuiFlexGroup when direction is ResizableLayoutDirection.Vertical', () => { - const component = mountComponent({ - direction: ResizableLayoutDirection.Vertical, - fixedPanel: <>, - flexPanel: <>, + renderComponent({ direction: ResizableLayoutDirection.Vertical }); + + expect(screen.getByTestId('resizableLayoutStaticContainer')).toHaveStyle({ + flexDirection: 'column', }); - expect(component.find(EuiFlexGroup).prop('direction')).toBe('column'); }); it('should pass direction "row" to EuiFlexGroup when direction is ResizableLayoutDirection.Horizontal', () => { - const component = mountComponent({ - direction: ResizableLayoutDirection.Horizontal, - fixedPanel: <>, - flexPanel: <>, + renderComponent({ direction: ResizableLayoutDirection.Horizontal }); + + expect(screen.getByTestId('resizableLayoutStaticContainer')).toHaveStyle({ + flexDirection: 'row', }); - expect(component.find(EuiFlexGroup).prop('direction')).toBe('row'); }); }); diff --git a/src/platform/packages/shared/kbn-resizable-layout/src/resizable_layout.test.tsx b/src/platform/packages/shared/kbn-resizable-layout/src/resizable_layout.test.tsx index f323d7a66c435..032790b44095e 100644 --- a/src/platform/packages/shared/kbn-resizable-layout/src/resizable_layout.test.tsx +++ b/src/platform/packages/shared/kbn-resizable-layout/src/resizable_layout.test.tsx @@ -7,98 +7,63 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { mount } from 'enzyme'; import type { ReactElement } from 'react'; import React from 'react'; +import { render, screen } from '@testing-library/react'; import { ResizableLayout } from './resizable_layout'; -import { PanelsResizable } from './panels_resizable'; -import { PanelsStatic } from './panels_static'; import { ResizableLayoutDirection, ResizableLayoutMode } from '../types'; -jest.mock('@elastic/eui', () => ({ - ...jest.requireActual('@elastic/eui'), - useResizeObserver: jest.fn(() => ({ width: 1000, height: 1000 })), -})); - describe('ResizableLayout component', () => { - const mountComponent = ({ - mode = ResizableLayoutMode.Resizable, + const renderComponent = ({ + fixedPanel =
Fixed panel
, + flexPanel =
Flex panel
, initialFixedPanelSize = 200, minFixedPanelSize = 100, minFlexPanelSize = 100, - fixedPanel = <>, - flexPanel = <>, + mode = ResizableLayoutMode.Resizable, }: { - mode?: ResizableLayoutMode; + fixedPanel?: ReactElement; + flexPanel?: ReactElement; initialFixedPanelSize?: number; minFixedPanelSize?: number; minFlexPanelSize?: number; - flexPanel?: ReactElement; - fixedPanel?: ReactElement; - }) => { - return mount( + mode?: ResizableLayoutMode; + } = {}) => { + return render( ); }; - it('should show PanelsFixed when mode is ResizableLayoutMode.Single', () => { - const fixedPanel =
; - const flexPanel =
; - const component = mountComponent({ mode: ResizableLayoutMode.Single, fixedPanel, flexPanel }); - expect(component.find(PanelsStatic).exists()).toBe(true); - expect(component.find(PanelsResizable).exists()).toBe(false); - expect(component.contains(fixedPanel)).toBe(false); - expect(component.contains(flexPanel)).toBe(true); - }); + it('should show only the flex panel when mode is ResizableLayoutMode.Single', () => { + renderComponent({ mode: ResizableLayoutMode.Single }); - it('should show PanelsFixed when mode is ResizableLayoutMode.Static', () => { - const fixedPanel =
; - const flexPanel =
; - const component = mountComponent({ mode: ResizableLayoutMode.Static, fixedPanel, flexPanel }); - expect(component.find(PanelsStatic).exists()).toBe(true); - expect(component.find(PanelsResizable).exists()).toBe(false); - expect(component.contains(fixedPanel)).toBe(true); - expect(component.contains(flexPanel)).toBe(true); + expect(screen.queryByText('Fixed panel')).not.toBeInTheDocument(); + expect(screen.getByText('Flex panel')).toBeVisible(); + expect(screen.queryByTestId('resizableLayoutResizableButton')).not.toBeInTheDocument(); }); - it('should show PanelsResizable when mode is ResizableLayoutMode.Resizable', () => { - const fixedPanel =
; - const flexPanel =
; - const component = mountComponent({ - mode: ResizableLayoutMode.Resizable, - fixedPanel, - flexPanel, - }); - expect(component.find(PanelsStatic).exists()).toBe(false); - expect(component.find(PanelsResizable).exists()).toBe(true); - expect(component.contains(fixedPanel)).toBe(true); - expect(component.contains(flexPanel)).toBe(true); - }); + it('should show both panels without resize controls when mode is ResizableLayoutMode.Static', () => { + renderComponent({ mode: ResizableLayoutMode.Static }); - it('should pass true for hideFixedPanel when mode is ResizableLayoutMode.Single', () => { - const fixedPanel =
; - const flexPanel =
; - const component = mountComponent({ mode: ResizableLayoutMode.Single, fixedPanel, flexPanel }); - expect(component.find(PanelsStatic).prop('hideFixedPanel')).toBe(true); - expect(component.contains(fixedPanel)).toBe(false); - expect(component.contains(flexPanel)).toBe(true); + expect(screen.getByText('Fixed panel')).toBeVisible(); + expect(screen.getByText('Flex panel')).toBeVisible(); + expect(screen.queryByTestId('resizableLayoutResizableButton')).not.toBeInTheDocument(); }); - it('should pass false for hideFixedPanel when mode is ResizableLayoutMode.Static', () => { - const fixedPanel =
; - const flexPanel =
; - const component = mountComponent({ mode: ResizableLayoutMode.Static, fixedPanel, flexPanel }); - expect(component.find(PanelsStatic).prop('hideFixedPanel')).toBe(false); - expect(component.contains(fixedPanel)).toBe(true); - expect(component.contains(flexPanel)).toBe(true); + it('should show both panels with resize controls when mode is ResizableLayoutMode.Resizable', () => { + renderComponent({ mode: ResizableLayoutMode.Resizable }); + + expect(screen.getByText('Fixed panel')).toBeVisible(); + expect(screen.getByText('Flex panel')).toBeVisible(); + expect(screen.getByTestId('resizableLayoutResizableButton')).toBeVisible(); }); }); diff --git a/src/platform/packages/shared/kbn-search-errors/src/__snapshots__/es_error.test.tsx.snap b/src/platform/packages/shared/kbn-search-errors/src/__snapshots__/es_error.test.tsx.snap deleted file mode 100644 index 68e53db73a99a..0000000000000 --- a/src/platform/packages/shared/kbn-search-errors/src/__snapshots__/es_error.test.tsx.snap +++ /dev/null @@ -1,13 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`EsError should render error message 1`] = ` -
-
-    
-      The supplied interval [2q] could not be parsed as a calendar interval.
-    
-  
-
-`; diff --git a/src/platform/packages/shared/kbn-search-errors/src/__snapshots__/painless_error.test.tsx.snap b/src/platform/packages/shared/kbn-search-errors/src/__snapshots__/painless_error.test.tsx.snap deleted file mode 100644 index 643973c636019..0000000000000 --- a/src/platform/packages/shared/kbn-search-errors/src/__snapshots__/painless_error.test.tsx.snap +++ /dev/null @@ -1,29 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Painless error should render error message 1`] = ` -
- - Error executing runtime field or scripted field on data view logs - - - - invalid -^---- HERE - - - cannot resolve symbol [invalid] - -
-`; diff --git a/src/platform/packages/shared/kbn-search-errors/src/__snapshots__/tsdb_error.test.tsx.snap b/src/platform/packages/shared/kbn-search-errors/src/__snapshots__/tsdb_error.test.tsx.snap deleted file mode 100644 index adc0412c175b7..0000000000000 --- a/src/platform/packages/shared/kbn-search-errors/src/__snapshots__/tsdb_error.test.tsx.snap +++ /dev/null @@ -1,18 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Tsdb error should render error message 1`] = ` -
-

- The field [bytes_counter] of Time series type [counter] has been used with the unsupported operation [sum]. -

- - See more about Time series field types and [counter] supported aggregations - -
-`; diff --git a/src/platform/packages/shared/kbn-search-errors/src/es_error.test.tsx b/src/platform/packages/shared/kbn-search-errors/src/es_error.test.tsx index 90ba3c13e3cc1..3c77c2456699c 100644 --- a/src/platform/packages/shared/kbn-search-errors/src/es_error.test.tsx +++ b/src/platform/packages/shared/kbn-search-errors/src/es_error.test.tsx @@ -7,12 +7,12 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import type { ReactElement } from 'react'; import type { CoreStart } from '@kbn/core/public'; +import { coreMock } from '@kbn/core/public/mocks'; import { createEsError } from './create_es_error'; +import { screen } from '@testing-library/react'; import { renderSearchError } from './render_search_error'; -import { shallow } from 'enzyme'; -import { coreMock } from '@kbn/core/public/mocks'; +import { renderWithKibanaRenderContext } from '@kbn/test-jest-helpers'; const services = { application: coreMock.createStart().application, @@ -45,21 +45,26 @@ describe('EsError', () => { services ); - test('should set error.message to root "error cause" reason', () => { + it('should set error.message to root "error cause" reason', () => { expect(esError.message).toEqual( 'The supplied interval [2q] could not be parsed as a calendar interval.' ); }); - test('should render error message', () => { + it('should render error message', () => { const searchErrorDisplay = renderSearchError(esError); expect(searchErrorDisplay).not.toBeUndefined(); - const wrapper = shallow(searchErrorDisplay?.body as ReactElement); - expect(wrapper).toMatchSnapshot(); + + renderWithKibanaRenderContext(searchErrorDisplay?.body); + + expect(screen.getByTestId('errMessage')).toHaveTextContent( + 'The supplied interval [2q] could not be parsed as a calendar interval.' + ); }); - test('should return 1 action', () => { + it('should return 1 action', () => { const searchErrorDisplay = renderSearchError(esError); + expect(searchErrorDisplay).not.toBeUndefined(); expect(searchErrorDisplay?.actions?.length).toBe(1); }); diff --git a/src/platform/packages/shared/kbn-search-errors/src/painless_error.test.tsx b/src/platform/packages/shared/kbn-search-errors/src/painless_error.test.tsx index c98fe2a467f2b..2ae2cc1e61ef1 100644 --- a/src/platform/packages/shared/kbn-search-errors/src/painless_error.test.tsx +++ b/src/platform/packages/shared/kbn-search-errors/src/painless_error.test.tsx @@ -7,13 +7,13 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import type { ReactElement } from 'react'; import type { CoreStart } from '@kbn/core/public'; -import type { DataView } from '@kbn/data-views-plugin/common'; +import { buildDataViewMock } from '@kbn/discover-utils/src/__mocks__'; +import { coreMock } from '@kbn/core/public/mocks'; import { createEsError } from './create_es_error'; +import { screen } from '@testing-library/react'; import { renderSearchError } from './render_search_error'; -import { shallow } from 'enzyme'; -import { coreMock } from '@kbn/core/public/mocks'; +import { renderWithKibanaRenderContext } from '@kbn/test-jest-helpers'; const servicesMock = { application: coreMock.createStart().application, @@ -26,10 +26,10 @@ const servicesMock = { } as CoreStart['docLinks'], }; -const dataViewMock = { - title: 'logs', +const dataViewMock = buildDataViewMock({ id: '1234', -} as unknown as DataView; + title: 'logs', +}); describe('Painless error', () => { const painlessError = createEsError( @@ -71,21 +71,29 @@ describe('Painless error', () => { dataViewMock ); - test('should set error.message to painless reason', () => { + it('should set error.message to painless reason', () => { expect(painlessError.message).toEqual( 'Error executing runtime field or scripted field on data view logs' ); }); - test('should render error message', () => { + it('should render error message', () => { const searchErrorDisplay = renderSearchError(painlessError); expect(searchErrorDisplay).not.toBeUndefined(); - const wrapper = shallow(searchErrorDisplay?.body as ReactElement); - expect(wrapper).toMatchSnapshot(); + + renderWithKibanaRenderContext(searchErrorDisplay?.body); + + expect( + screen.getByText('Error executing runtime field or scripted field on data view logs') + ).toBeVisible(); + expect(screen.getByText('cannot resolve symbol [invalid]')).toBeVisible(); + expect(screen.getByTestId('painlessStackTrace')).toHaveTextContent('invalid'); + expect(screen.getByTestId('painlessStackTrace')).toHaveTextContent('^---- HERE'); }); - test('should return 2 actions', () => { + it('should return 2 actions', () => { const searchErrorDisplay = renderSearchError(painlessError); + expect(searchErrorDisplay).not.toBeUndefined(); expect(searchErrorDisplay?.actions?.length).toBe(2); }); diff --git a/src/platform/packages/shared/kbn-search-errors/src/tsdb_error.test.tsx b/src/platform/packages/shared/kbn-search-errors/src/tsdb_error.test.tsx index 86c35608dd7ef..49e05b7623882 100644 --- a/src/platform/packages/shared/kbn-search-errors/src/tsdb_error.test.tsx +++ b/src/platform/packages/shared/kbn-search-errors/src/tsdb_error.test.tsx @@ -7,12 +7,12 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import type { ReactElement } from 'react'; import type { CoreStart } from '@kbn/core/public'; +import { coreMock } from '@kbn/core/public/mocks'; import { createEsError } from './create_es_error'; import { renderSearchError } from './render_search_error'; -import { shallow } from 'enzyme'; -import { coreMock } from '@kbn/core/public/mocks'; +import { renderWithKibanaRenderContext } from '@kbn/test-jest-helpers'; +import { screen } from '@testing-library/react'; const servicesMock = { application: coreMock.createStart().application, @@ -68,21 +68,33 @@ describe('Tsdb error', () => { servicesMock ); - test('should set error.message to tsdb reason', () => { + it('should set error.message to tsdb reason', () => { expect(tsdbError.message).toEqual( 'The field [bytes_counter] of Time series type [counter] has been used with the unsupported operation [sum].' ); }); - test('should render error message', () => { + it('should render error message', () => { const searchErrorDisplay = renderSearchError(tsdbError); expect(searchErrorDisplay).not.toBeUndefined(); - const wrapper = shallow(searchErrorDisplay?.body as ReactElement); - expect(wrapper).toMatchSnapshot(); + + renderWithKibanaRenderContext(searchErrorDisplay?.body); + + expect( + screen.getByText( + 'The field [bytes_counter] of Time series type [counter] has been used with the unsupported operation [sum].' + ) + ).toBeVisible(); + expect( + screen.getByText( + 'See more about Time series field types and [counter] supported aggregations' + ) + ).toBeVisible(); }); - test('should return 1 actions', () => { + it('should return 1 actions', () => { const searchErrorDisplay = renderSearchError(tsdbError); + expect(searchErrorDisplay).not.toBeUndefined(); expect(searchErrorDisplay?.actions?.length).toBe(1); }); diff --git a/src/platform/packages/shared/kbn-search-errors/tsconfig.json b/src/platform/packages/shared/kbn-search-errors/tsconfig.json index 9ea6c6e1fd5c8..86913d16226ea 100644 --- a/src/platform/packages/shared/kbn-search-errors/tsconfig.json +++ b/src/platform/packages/shared/kbn-search-errors/tsconfig.json @@ -5,7 +5,8 @@ "types": [ "jest", "node", - "react" + "react", + "@testing-library/jest-dom" ] }, "include": [ diff --git a/src/platform/packages/shared/kbn-unified-data-table/src/components/build_copy_column_button.test.tsx b/src/platform/packages/shared/kbn-unified-data-table/src/components/build_copy_column_button.test.tsx index cec074e85c499..71f1e3297c785 100644 --- a/src/platform/packages/shared/kbn-unified-data-table/src/components/build_copy_column_button.test.tsx +++ b/src/platform/packages/shared/kbn-unified-data-table/src/components/build_copy_column_button.test.tsx @@ -8,113 +8,126 @@ */ import React from 'react'; +import userEvent from '@testing-library/user-event'; +import { buildCopyColumnNameButton, buildCopyColumnValuesButton } from './build_copy_column_button'; +import { dataTableContextMock } from '../../__mocks__/table_context'; import { EuiButton } from '@elastic/eui'; -import { mountWithIntl } from '@kbn/test-jest-helpers'; +import { renderWithI18n } from '@kbn/test-jest-helpers'; +import { screen } from '@testing-library/react'; import { servicesMock } from '../../__mocks__/services'; -import { dataTableContextMock } from '../../__mocks__/table_context'; -import { buildCopyColumnNameButton, buildCopyColumnValuesButton } from './build_copy_column_button'; const execCommandMock = (global.document.execCommand = jest.fn()); -const warn = jest.spyOn(console, 'warn').mockImplementation(() => {}); +const originalClipboard = global.window.navigator.clipboard; + +const mockClipboard = () => { + const writeText = jest.fn(); + + Object.assign(navigator, { + clipboard: { writeText }, + }); + + return writeText; +}; describe('Build a column button to copy to clipboard', () => { - it('should copy a column name to clipboard on click', () => { - const { label, iconType, onClick } = buildCopyColumnNameButton({ + beforeEach(() => { + jest.clearAllMocks(); + }); + + afterEach(() => { + jest.restoreAllMocks(); + Object.assign(navigator, { + clipboard: originalClipboard, + }); + }); + + it('should copy a column name to clipboard on click', async () => { + const { iconType, label, onClick } = buildCopyColumnNameButton({ columnDisplayName: 'test-field-name', toastNotifications: servicesMock.toastNotifications, }); execCommandMock.mockImplementationOnce(() => true); - const wrapper = mountWithIntl( + renderWithI18n( {label} ); - wrapper.find('button').simulate('click'); + await userEvent.click(screen.getByRole('button', { name: 'Copy name' })); expect(execCommandMock).toHaveBeenCalledWith('copy'); - expect(warn).not.toHaveBeenCalled(); + expect(servicesMock.toastNotifications.addWarning).not.toHaveBeenCalled(); }); - it('should copy column values to clipboard on click', async () => { - const originalClipboard = global.window.navigator.clipboard; - - Object.defineProperty(navigator, 'clipboard', { - value: { - writeText: jest.fn(), - }, - writable: true, - }); - - const { label, iconType, onClick } = buildCopyColumnValuesButton({ + it('should copy regular column values to clipboard on click', async () => { + const writeText = mockClipboard(); + const { iconType, label, onClick } = buildCopyColumnValuesButton({ columnId: 'extension', columnDisplayName: 'custom_extension', - toastNotifications: servicesMock.toastNotifications, rowsCount: 3, + toastNotifications: servicesMock.toastNotifications, valueToStringConverter: dataTableContextMock.valueToStringConverter, }); - const wrapper = mountWithIntl( + renderWithI18n( {label} ); - await wrapper.find('button').simulate('click'); + await userEvent.click(screen.getByRole('button', { name: 'Copy column' })); // first row out of 3 rows does not have a value - expect(navigator.clipboard.writeText).toHaveBeenCalledWith('"custom_extension"\n\njpg\ngif'); + expect(writeText).toHaveBeenCalledWith('"custom_extension"\n\njpg\ngif'); + }); - const { - label: labelSource, - iconType: iconTypeSource, - onClick: onClickSource, - } = buildCopyColumnValuesButton({ - columnId: '_source', + it('should copy source column values to clipboard on click', async () => { + const writeText = mockClipboard(); + const { iconType, label, onClick } = buildCopyColumnValuesButton({ columnDisplayName: 'Document', + columnId: '_source', + rowsCount: 3, toastNotifications: servicesMock.toastNotifications, valueToStringConverter: dataTableContextMock.valueToStringConverter, - rowsCount: 3, }); - const wrapperSource = mountWithIntl( - - {labelSource} + renderWithI18n( + + {label} ); - await wrapperSource.find('button').simulate('click'); + await userEvent.click(screen.getByRole('button', { name: 'Copy column' })); // first row out of 3 rows does not have a value - expect(navigator.clipboard.writeText).toHaveBeenNthCalledWith( - 2, + expect(writeText).toHaveBeenCalledWith( 'Document\n{"bytes":20,"date":"2020-20-01T12:12:12.123","message":"test1","_index":"i","_score":1}\n' + '{"date":"2020-20-01T12:12:12.124","extension":"jpg","name":"test2","_index":"i","_score":1}\n' + '{"bytes":50,"date":"2020-20-01T12:12:12.124","extension":"gif","name":"test3","_index":"i","_score":1}' ); - - Object.defineProperty(navigator, 'clipboard', { - value: originalClipboard, - }); }); - it('should not copy to clipboard on click', () => { + it('should not copy to clipboard on click', async () => { const { label, iconType, onClick } = buildCopyColumnNameButton({ columnDisplayName: 'test-field-name', toastNotifications: servicesMock.toastNotifications, }); execCommandMock.mockImplementationOnce(() => false); + const warn = jest.spyOn(console, 'warn').mockImplementation(() => {}); - const wrapper = mountWithIntl( + renderWithI18n( {label} ); - wrapper.find('button').simulate('click'); + await userEvent.click(screen.getByRole('button', { name: 'Copy name' })); expect(execCommandMock).toHaveBeenCalledWith('copy'); expect(warn).toHaveBeenCalledWith('Unable to copy to clipboard.'); + expect(servicesMock.toastNotifications.addWarning).toHaveBeenCalledWith({ + title: 'Unable to copy to clipboard in this browser', + }); }); }); diff --git a/src/platform/packages/shared/kbn-unified-data-table/src/components/build_edit_field_button.test.tsx b/src/platform/packages/shared/kbn-unified-data-table/src/components/build_edit_field_button.test.tsx index 5b80f4f72f81a..7f6e562d9b6e5 100644 --- a/src/platform/packages/shared/kbn-unified-data-table/src/components/build_edit_field_button.test.tsx +++ b/src/platform/packages/shared/kbn-unified-data-table/src/components/build_edit_field_button.test.tsx @@ -7,56 +7,43 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ +import type { DataViewField } from '@kbn/data-views-plugin/common'; import type { EuiListGroupItemProps } from '@elastic/eui'; -import { EuiListGroupItem } from '@elastic/eui'; -import type { DataView, DataViewField } from '@kbn/data-views-plugin/common'; -import { mountWithIntl } from '@kbn/test-jest-helpers'; import React from 'react'; -import { buildDataViewMock } from '@kbn/discover-utils/src/__mocks__'; -import { servicesMock } from '../../__mocks__/services'; +import userEvent from '@testing-library/user-event'; import { buildEditFieldButton } from './build_edit_field_button'; +import { dataViewMock } from '@kbn/discover-utils/src/__mocks__'; +import { EuiListGroupItem } from '@elastic/eui'; +import { renderWithI18n } from '@kbn/test-jest-helpers'; +import { screen } from '@testing-library/react'; +import { servicesMock } from '../../__mocks__/services'; + +const getField = (name: string) => dataViewMock.getFieldByName(name) as DataViewField; -const dataView = buildDataViewMock({ - name: 'test-index-view', - fields: [ - { - name: '_source', - type: '_source', - }, - { - name: 'unknown_field', - type: 'unknown', - }, - { - name: 'unknown_selected_field', - type: 'unknown', - }, - { - name: 'bytes', - type: 'number', - }, - { - name: 'runtime_field', - type: 'unknown', - runtimeField: { - type: 'unknown', - script: { - source: "emit('hello world')", - }, - }, - }, - ] as DataView['fields'], +const unknownField = dataViewMock.fields.create({ + aggregatable: false, + name: 'unknown_field', + searchable: false, + type: 'unknown', }); describe('buildEditFieldButton', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + it('should return null if the field is not editable', () => { - const field = dataView.getFieldByName('unknown_field') as DataViewField; const button = buildEditFieldButton({ - hasEditDataViewPermission: () => servicesMock.dataViewEditor.userPermissions.editDataView(), - dataView, - field, + dataView: dataViewMock, editField: jest.fn(), + field: unknownField, + hasEditDataViewPermission: () => servicesMock.dataViewEditor.userPermissions.editDataView(), }); + expect(button).toBeNull(); }); @@ -64,63 +51,66 @@ describe('buildEditFieldButton', () => { jest .spyOn(servicesMock.dataViewEditor.userPermissions, 'editDataView') .mockReturnValueOnce(false); - const field = dataView.getFieldByName('bytes') as DataViewField; + + const field = getField('bytes'); const button = buildEditFieldButton({ - hasEditDataViewPermission: () => servicesMock.dataViewEditor.userPermissions.editDataView(), - dataView, - field, + dataView: dataViewMock, editField: jest.fn(), + field, + hasEditDataViewPermission: () => servicesMock.dataViewEditor.userPermissions.editDataView(), }); + expect(button).toBeNull(); }); it('should return null if passed the _source field', () => { - const field = dataView.getFieldByName('_source') as DataViewField; + const field = getField('_source'); const button = buildEditFieldButton({ - hasEditDataViewPermission: () => servicesMock.dataViewEditor.userPermissions.editDataView(), - dataView, - field, + dataView: dataViewMock, editField: jest.fn(), + field, + hasEditDataViewPermission: () => servicesMock.dataViewEditor.userPermissions.editDataView(), }); + expect(button).toBeNull(); }); it('should return EuiListGroupItemProps if the field and data view are editable', () => { - const field = dataView.getFieldByName('bytes') as DataViewField; + const field = getField('bytes'); const button = buildEditFieldButton({ - hasEditDataViewPermission: () => servicesMock.dataViewEditor.userPermissions.editDataView(), - dataView, - field, + dataView: dataViewMock, editField: jest.fn(), - }); + field, + hasEditDataViewPermission: () => servicesMock.dataViewEditor.userPermissions.editDataView(), + }) as EuiListGroupItemProps; + expect(button).not.toBeNull(); - expect(button).toMatchInlineSnapshot(` - Object { - "data-test-subj": "gridEditFieldButton", - "iconProps": Object { - "size": "m", - }, - "iconType": "pencil", - "label": , - "onClick": [Function], - } - `); + expect(button).toMatchObject({ + 'data-test-subj': 'gridEditFieldButton', + iconProps: { size: 'm' }, + iconType: 'pencil', + }); + expect(button.onClick).toEqual(expect.any(Function)); + + renderWithI18n(); + + expect(screen.getByText('Edit data view field')).toBeVisible(); }); - it('should call editField when onClick is triggered', () => { - const field = dataView.getFieldByName('bytes') as DataViewField; + it('should call editField when onClick is triggered', async () => { const editField = jest.fn(); + const field = getField('bytes'); const buttonProps = buildEditFieldButton({ - hasEditDataViewPermission: () => servicesMock.dataViewEditor.userPermissions.editDataView(), - dataView, - field, + dataView: dataViewMock, editField, + field, + hasEditDataViewPermission: () => servicesMock.dataViewEditor.userPermissions.editDataView(), }) as EuiListGroupItemProps; - const listItem = mountWithIntl(); - listItem.find('button').simulate('click'); + + renderWithI18n(); + + await userEvent.click(screen.getByText('Edit data view field')); + expect(editField).toHaveBeenCalledTimes(1); expect(editField).toHaveBeenCalledWith('bytes'); }); diff --git a/src/platform/packages/shared/kbn-unified-data-table/src/components/custom_control_columns/color_indicator/color_indicator_control_column.test.tsx b/src/platform/packages/shared/kbn-unified-data-table/src/components/custom_control_columns/color_indicator/color_indicator_control_column.test.tsx index f8ef651a40616..548ad3b8fc210 100644 --- a/src/platform/packages/shared/kbn-unified-data-table/src/components/custom_control_columns/color_indicator/color_indicator_control_column.test.tsx +++ b/src/platform/packages/shared/kbn-unified-data-table/src/components/custom_control_columns/color_indicator/color_indicator_control_column.test.tsx @@ -8,37 +8,38 @@ */ import React from 'react'; -import { mountWithIntl } from '@kbn/test-jest-helpers'; -import type { EuiDataGridCellValueElementProps } from '@elastic/eui'; -import { getColorIndicatorControlColumn } from './color_indicator_control_column'; import { dataTableContextMock } from '../../../../__mocks__/table_context'; +import { getColorIndicatorControlColumn } from './color_indicator_control_column'; +import { renderWithI18n } from '@kbn/test-jest-helpers'; +import { screen } from '@testing-library/react'; import { UnifiedDataTableContext } from '../../../table_context'; describe('ColorIndicatorControlColumn', () => { - const contextMock = { - ...dataTableContextMock, - }; - it('should render the component', () => { const getRowIndicator = jest.fn(() => ({ color: 'red', label: 'error' })); const column = getColorIndicatorControlColumn({ getRowIndicator, }); - const ColorIndicatorControlColumn = - column.rowCellRender as React.FC; - mountWithIntl( - + const ColorIndicatorControlColumn = column.rowCellRender; + + renderWithI18n( + ); - expect(getRowIndicator).toHaveBeenCalledWith(contextMock.getRowByIndex(1), expect.any(Object)); + + expect(screen.getByTestId('unifiedDataTableRowColorIndicatorCell')).toBeVisible(); + expect(getRowIndicator).toHaveBeenCalledWith( + dataTableContextMock.getRowByIndex(1), + expect.any(Object) + ); }); }); diff --git a/src/platform/packages/shared/kbn-unified-data-table/src/components/data_table_additional_display_settings.test.tsx b/src/platform/packages/shared/kbn-unified-data-table/src/components/data_table_additional_display_settings.test.tsx index d0c2ac3aac671..890452de8a1ca 100644 --- a/src/platform/packages/shared/kbn-unified-data-table/src/components/data_table_additional_display_settings.test.tsx +++ b/src/platform/packages/shared/kbn-unified-data-table/src/components/data_table_additional_display_settings.test.tsx @@ -7,103 +7,80 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import React from 'react'; -import { mountWithIntl } from '@kbn/test-jest-helpers'; -import { act } from 'react-dom/test-utils'; -import { findTestSubject } from '@elastic/eui/lib/test'; import type { UnifiedDataTableAdditionalDisplaySettingsProps } from './data_table_additional_display_settings'; -import { UnifiedDataTableAdditionalDisplaySettings } from './data_table_additional_display_settings'; import lodash from 'lodash'; -import { fireEvent, render, screen } from '@testing-library/react'; +import React from 'react'; import userEvent from '@testing-library/user-event'; +import { render, screen } from '@testing-library/react'; import { RowHeightMode } from './row_height_settings'; +import { UnifiedDataTableAdditionalDisplaySettings } from './data_table_additional_display_settings'; jest.spyOn(lodash, 'debounce').mockImplementation((fn: any) => fn); +const defaultDisplaySettingsProps = { + headerLineCountInput: 5, + headerRowHeight: RowHeightMode.custom, + lineCountInput: 10, + rowHeight: RowHeightMode.custom, + sampleSize: 10, +}; + +const getSampleSizeNumberInput = () => screen.getByRole('spinbutton', { name: 'Sample size' }); + const renderDisplaySettings = ( props: Partial = {} ) => { return render( - + ); }; -describe('UnifiedDataTableAdditionalDisplaySettings', function () { - describe('sampleSize', function () { +const replaceNumberInputValue = async (input: HTMLElement, value: string) => { + await userEvent.clear(input); + await userEvent.click(input); + await userEvent.paste(value); +}; + +describe('UnifiedDataTableAdditionalDisplaySettings', () => { + describe('sampleSize', () => { it('should work correctly', async () => { const onChangeSampleSizeMock = jest.fn(); - const component = mountWithIntl( - - ); - const input = findTestSubject(component, 'unifiedDataTableSampleSizeInput').last(); - expect(input.prop('value')).toBe(10); - expect(input.prop('step')).toBe(10); - - await act(async () => { - input.simulate('change', { - target: { - value: 100, - }, - }); + renderDisplaySettings({ + sampleSize: 10, + onChangeSampleSize: onChangeSampleSizeMock, }); - expect(onChangeSampleSizeMock).toHaveBeenCalledWith(100); + expect(screen.getByText('Sample size')).toBeVisible(); + + const input = getSampleSizeNumberInput(); + expect(input).toHaveValue(10); + expect(input).toHaveAttribute('step', '10'); + + await replaceNumberInputValue(input, '100'); - await new Promise((resolve) => setTimeout(resolve, 0)); - component.update(); + expect(onChangeSampleSizeMock).toHaveBeenLastCalledWith(100); - expect( - findTestSubject(component, 'unifiedDataTableSampleSizeInput').last().prop('value') - ).toBe(100); + expect(getSampleSizeNumberInput()).toHaveValue(100); }); it('should not execute the callback for an invalid input', async () => { const invalidValue = 600; const onChangeSampleSizeMock = jest.fn(); - const component = mountWithIntl( - - ); - const input = findTestSubject(component, 'unifiedDataTableSampleSizeInput').last(); - expect(input.prop('value')).toBe(50); - - await act(async () => { - input.simulate('change', { - target: { - value: invalidValue, - }, - }); + renderDisplaySettings({ + maxAllowedSampleSize: 500, + onChangeSampleSize: onChangeSampleSizeMock, + sampleSize: 50, }); - await new Promise((resolve) => setTimeout(resolve, 0)); - component.update(); + const input = getSampleSizeNumberInput(); + expect(screen.getByText('Sample size')).toBeVisible(); + expect(input).toHaveValue(50); + + await replaceNumberInputValue(input, String(invalidValue)); - expect( - findTestSubject(component, 'unifiedDataTableSampleSizeInput').last().prop('value') - ).toBe(invalidValue); + expect(getSampleSizeNumberInput()).toHaveValue(invalidValue); expect(onChangeSampleSizeMock).not.toHaveBeenCalled(); }); @@ -111,32 +88,22 @@ describe('UnifiedDataTableAdditionalDisplaySettings', function () { it('should render value changes correctly', async () => { const onChangeSampleSizeMock = jest.fn(); - const component = mountWithIntl( + const { rerender } = renderDisplaySettings({ + onChangeSampleSize: onChangeSampleSizeMock, + sampleSize: 200, + }); + + expect(getSampleSizeNumberInput()).toHaveValue(200); + + rerender( ); - expect( - findTestSubject(component, 'unifiedDataTableSampleSizeInput').last().prop('value') - ).toBe(200); - - component.setProps({ - sampleSize: 500, - onChangeSampleSize: onChangeSampleSizeMock, - }); - - await new Promise((resolve) => setTimeout(resolve, 0)); - component.update(); - - expect( - findTestSubject(component, 'unifiedDataTableSampleSizeInput').last().prop('value') - ).toBe(500); + expect(getSampleSizeNumberInput()).toHaveValue(500); expect(onChangeSampleSizeMock).not.toHaveBeenCalled(); }); @@ -144,37 +111,21 @@ describe('UnifiedDataTableAdditionalDisplaySettings', function () { it('should only render integers when a decimal value is provided', async () => { const invalidDecimalValue = 6.11; const validIntegerValue = 6; - const onChangeSampleSizeMock = jest.fn(); - const component = mountWithIntl( - - ); - const input = findTestSubject(component, 'unifiedDataTableSampleSizeInput').last(); - expect(input.prop('value')).toBe(50); - - await act(async () => { - input.simulate('change', { - target: { - value: invalidDecimalValue, - }, - }); + renderDisplaySettings({ + maxAllowedSampleSize: 500, + sampleSize: 50, + onChangeSampleSize: onChangeSampleSizeMock, }); - await new Promise((resolve) => setTimeout(resolve, 0)); - component.update(); + const input = getSampleSizeNumberInput(); + expect(input).toHaveValue(50); + expect(screen.getByText('Sample size')).toBeVisible(); + + await replaceNumberInputValue(input, String(invalidDecimalValue)); - expect( - findTestSubject(component, 'unifiedDataTableSampleSizeInput').last().prop('value') - ).toBe(validIntegerValue); + expect(getSampleSizeNumberInput()).toHaveValue(validIntegerValue); }); it('should not fail if sample size is not step of 10', async () => { @@ -183,38 +134,24 @@ describe('UnifiedDataTableAdditionalDisplaySettings', function () { const customSampleSize = 9995; const newSampleSize = 9990; - const component = mountWithIntl( - - ); - - let input = findTestSubject(component, 'unifiedDataTableSampleSizeInput').last(); - expect(input.prop('value')).toBe(customSampleSize); - expect(input.prop('step')).toBe(1); - - await act(async () => { - input.simulate('change', { - target: { - value: newSampleSize, - }, - }); + renderDisplaySettings({ + maxAllowedSampleSize: customSampleSize, + onChangeSampleSize: onChangeSampleSizeMock, + sampleSize: customSampleSize, }); - await new Promise((resolve) => setTimeout(resolve, 0)); - component.update(); + let input = getSampleSizeNumberInput(); + expect(input).toHaveValue(customSampleSize); + expect(input).toHaveAttribute('step', '1'); + expect(screen.getByText('Sample size')).toBeVisible(); - input = findTestSubject(component, 'unifiedDataTableSampleSizeInput').last(); - expect(input.prop('value')).toBe(newSampleSize); - expect(input.prop('step')).toBe(1); + await replaceNumberInputValue(input, String(newSampleSize)); - expect(onChangeSampleSizeMock).toHaveBeenCalledWith(newSampleSize); + input = getSampleSizeNumberInput(); + expect(input).toHaveValue(newSampleSize); + expect(input).toHaveAttribute('step', '1'); + + expect(onChangeSampleSizeMock).toHaveBeenLastCalledWith(newSampleSize); }); it('should not fail if sample size is less than 10', async () => { @@ -223,36 +160,23 @@ describe('UnifiedDataTableAdditionalDisplaySettings', function () { const customSampleSize = 5; const newSampleSize = 10; - const component = mountWithIntl( - - ); - let input = findTestSubject(component, 'unifiedDataTableSampleSizeInput').last(); - expect(input.prop('value')).toBe(customSampleSize); - expect(input.prop('step')).toBe(1); - - await act(async () => { - input.simulate('change', { - target: { - value: newSampleSize, - }, - }); + renderDisplaySettings({ + onChangeSampleSize: onChangeSampleSizeMock, + sampleSize: customSampleSize, }); - await new Promise((resolve) => setTimeout(resolve, 0)); - component.update(); + let input = getSampleSizeNumberInput(); + expect(input).toHaveValue(customSampleSize); + expect(input).toHaveAttribute('step', '1'); + expect(screen.getByText('Sample size')).toBeVisible(); + + await replaceNumberInputValue(input, String(newSampleSize)); - input = findTestSubject(component, 'unifiedDataTableSampleSizeInput').last(); - expect(input.prop('value')).toBe(newSampleSize); - expect(input.prop('step')).toBe(1); + input = getSampleSizeNumberInput(); + expect(input).toHaveValue(newSampleSize); + expect(input).toHaveAttribute('step', '1'); - expect(onChangeSampleSizeMock).toHaveBeenCalledWith(newSampleSize); + expect(onChangeSampleSizeMock).toHaveBeenLastCalledWith(newSampleSize); }); }); @@ -262,25 +186,49 @@ describe('UnifiedDataTableAdditionalDisplaySettings', function () { onChangeRowHeight: jest.fn(), onChangeRowHeightLines: jest.fn(), }); - expect(screen.getByLabelText('Body cell lines')).toBeInTheDocument(); + + expect(screen.getByLabelText('Body cell lines')).toBeVisible(); + expect(screen.getByText('Custom')).toBeVisible(); + expect(screen.getByText('Auto')).toBeVisible(); }); it('should not render rowHeight if onChangeRowHeight and onChangeRowHeightLines are undefined', () => { renderDisplaySettings(); + expect(screen.queryByLabelText('Body cell lines')).not.toBeInTheDocument(); }); it('should call onChangeRowHeight and onChangeRowHeightLines when the rowHeight changes', async () => { const onChangeRowHeight = jest.fn(); const onChangeRowHeightLines = jest.fn(); - renderDisplaySettings({ - rowHeight: RowHeightMode.custom, + + const { rerender } = renderDisplaySettings({ onChangeRowHeight, onChangeRowHeightLines, }); - fireEvent.change(screen.getByRole('spinbutton'), { target: { value: 5 } }); + + expect(screen.getByLabelText('Body cell lines')).toBeVisible(); + + const input = screen.getByRole('spinbutton'); + + await userEvent.clear(input); + + rerender( + + ); + + await userEvent.click(input); + await userEvent.paste('5'); + expect(onChangeRowHeightLines).toHaveBeenCalledWith(5, true); + await userEvent.click(screen.getByRole('button', { name: 'Auto' })); + expect(onChangeRowHeight).toHaveBeenCalledWith('auto'); }); }); @@ -291,25 +239,46 @@ describe('UnifiedDataTableAdditionalDisplaySettings', function () { onChangeHeaderRowHeight: jest.fn(), onChangeHeaderRowHeightLines: jest.fn(), }); - expect(screen.getByLabelText('Max header cell lines')).toBeInTheDocument(); + + expect(screen.getByLabelText('Max header cell lines')).toBeVisible(); }); it('should not render headerRowHeight if onChangeHeaderRowHeight and onChangeHeaderRowHeightLines are undefined', () => { renderDisplaySettings(); + expect(screen.queryByLabelText('Max header cell lines')).not.toBeInTheDocument(); }); it('should call onChangeHeaderRowHeight and onChangeHeaderRowHeightLines when the headerRowHeight changes', async () => { const onChangeHeaderRowHeight = jest.fn(); const onChangeHeaderRowHeightLines = jest.fn(); - renderDisplaySettings({ - headerRowHeight: RowHeightMode.custom, + + const { rerender } = renderDisplaySettings({ onChangeHeaderRowHeight, onChangeHeaderRowHeightLines, }); - fireEvent.change(screen.getByRole('spinbutton'), { target: { value: 3 } }); + + expect(screen.getByLabelText('Max header cell lines')).toBeVisible(); + + const input = screen.getByRole('spinbutton'); + await userEvent.clear(input); + + rerender( + + ); + + await userEvent.click(input); + await userEvent.paste('3'); + expect(onChangeHeaderRowHeightLines).toHaveBeenCalledWith(3, true); + await userEvent.click(screen.getByRole('button', { name: 'Auto' })); + expect(onChangeHeaderRowHeight).toHaveBeenCalledWith('auto'); }); }); diff --git a/src/platform/packages/shared/kbn-unified-data-table/src/components/data_table_column_header.test.tsx b/src/platform/packages/shared/kbn-unified-data-table/src/components/data_table_column_header.test.tsx index 7a05d99ddff49..5a864afa55050 100644 --- a/src/platform/packages/shared/kbn-unified-data-table/src/components/data_table_column_header.test.tsx +++ b/src/platform/packages/shared/kbn-unified-data-table/src/components/data_table_column_header.test.tsx @@ -7,16 +7,14 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import type { ReactElement } from 'react'; import React from 'react'; -import { FieldIcon } from '@kbn/field-utils'; -import { mountWithI18nProvider } from '@kbn/test-jest-helpers'; import { createStubDataView, stubLogstashDataView, } from '@kbn/data-views-plugin/common/data_view.stub'; import { DataTableColumnHeader } from './data_table_column_header'; -import { waitFor } from '@testing-library/react'; +import { renderWithKibanaRenderContext } from '@kbn/test-jest-helpers'; +import { screen } from '@testing-library/react'; const stubDataViewWithNested = createStubDataView({ spec: { @@ -54,32 +52,24 @@ const stubDataViewWithNested = createStubDataView({ }, }); -describe('DataTableColumnHeader', function () { - async function mountComponent(element: ReactElement) { - const component = mountWithI18nProvider(element); - // wait for lazy modules - await new Promise((resolve) => setTimeout(resolve, 5)); - component.update(); - - return component; - } +describe('DataTableColumnHeader', () => { it('should render a correct token', async () => { - const component = await mountComponent( + renderWithKibanaRenderContext( ); - expect(component.find(FieldIcon).first().prop('type')).toBe('number'); - await waitFor(() => expect(component.text()).toBe('NumberbytesDisplayName')); + + expect(await screen.findByText('Number')).toBeVisible(); + expect(screen.getByText('bytesDisplayName')).toBeVisible(); }); it('should render a correct token for a custom column type (in case of text-based queries)', async () => { - const component = await mountComponent( + renderWithKibanaRenderContext( ); - expect(component.text()).toBe('KeywordbytesDisplayName'); - expect(component.find(FieldIcon).first().prop('type')).toBe('keyword'); + + expect(await screen.findByText('Keyword')).toBeVisible(); + expect(screen.getByText('bytesDisplayName')).toBeVisible(); }); - it('should not render a token for Document column', async () => { - const component = await mountComponent( + it('should not render a token for Document column', () => { + renderWithKibanaRenderContext( ); - expect(component.text()).toBe('Document'); - expect(component.find(FieldIcon).exists()).toBe(false); + + expect(screen.getByText('Document')).toBeVisible(); + expect(screen.queryByText('Number')).not.toBeInTheDocument(); + expect(screen.queryByText('Keyword')).not.toBeInTheDocument(); + expect(screen.queryByText('Nested')).not.toBeInTheDocument(); }); it('should render the nested icon', async () => { - const component = await mountComponent( + renderWithKibanaRenderContext( ); - expect(component.text()).toBe('NestedNested User'); - expect(component.find(FieldIcon).first().prop('type')).toBe('nested'); + + expect(await screen.findByText('Nested')).toBeVisible(); + expect(screen.getByText('Nested User')).toBeVisible(); }); }); diff --git a/src/platform/packages/shared/kbn-unified-data-table/src/components/data_table_document_selection.test.tsx b/src/platform/packages/shared/kbn-unified-data-table/src/components/data_table_document_selection.test.tsx index 54d8bd1cac4d2..1e9207081bb16 100644 --- a/src/platform/packages/shared/kbn-unified-data-table/src/components/data_table_document_selection.test.tsx +++ b/src/platform/packages/shared/kbn-unified-data-table/src/components/data_table_document_selection.test.tsx @@ -7,257 +7,229 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import React from 'react'; -import { act } from 'react-dom/test-utils'; -import { mountWithIntl } from '@kbn/test-jest-helpers'; -import { findTestSubject } from '@elastic/eui/lib/test'; import type { DataTableCompareToolbarBtn } from './data_table_document_selection'; -import { - DataTableDocumentToolbarBtn, - SelectButton, - getSelectAllButton, -} from './data_table_document_selection'; +import React from 'react'; +import userEvent from '@testing-library/user-event'; import { buildSelectedDocsState, dataTableContextMock, dataTableContextRowsMock, } from '../../__mocks__/table_context'; -import { UnifiedDataTableContext } from '../table_context'; +import { + DataTableDocumentToolbarBtn, + SelectButton, + getSelectAllButton, +} from './data_table_document_selection'; import { getDocId } from '@kbn/discover-utils'; -import { render, screen } from '@testing-library/react'; -import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; +import { renderWithI18n } from '@kbn/test-jest-helpers'; +import { screen } from '@testing-library/react'; import { servicesMock } from '../../__mocks__/services'; -import userEvent from '@testing-library/user-event'; +import { UnifiedDataTableContext } from '../table_context'; + +const getSelectionToolbarButton = (selectedCount: number) => + screen.getByRole('button', { name: new RegExp(`Selected.*Active:\\s*${selectedCount}`) }); + +const renderWithTableContext = (ui: React.ReactElement, contextMock = dataTableContextMock) => + renderWithI18n( + {ui} + ); describe('document selection', () => { describe('getDocId', () => { - test('doc with custom routing', () => { + it('doc with custom routing', () => { const doc = { _id: 'test-id', _index: 'test-indices', _routing: 'why-not', }; - expect(getDocId(doc)).toMatchInlineSnapshot(`"test-indices::test-id::why-not"`); + + expect(getDocId(doc)).toBe('test-indices::test-id::why-not'); }); - test('doc without custom routing', () => { + it('doc without custom routing', () => { const doc = { _id: 'test-id', _index: 'test-indices', }; - expect(getDocId(doc)).toMatchInlineSnapshot(`"test-indices::test-id::"`); + + expect(getDocId(doc)).toBe('test-indices::test-id::'); }); }); describe('SelectAllButton', () => { - test('is not checked', () => { + it('is not checked', () => { const contextMock = { ...dataTableContextMock, }; + const SelectAllButton = getSelectAllButton(dataTableContextRowsMock); - const component = mountWithIntl( - - - - ); + renderWithTableContext(, contextMock); - const checkBox = findTestSubject(component, 'selectAllDocsOnPageToggle'); - expect(checkBox.props().checked).toBeFalsy(); + expect(screen.getByRole('checkbox', { name: 'Select all visible rows' })).not.toBeChecked(); }); - test('is checked correctly', () => { + it('is checked correctly', () => { const contextMock = { ...dataTableContextMock, selectedDocsState: buildSelectedDocsState(['i::1::']), }; + const SelectAllButton = getSelectAllButton(dataTableContextRowsMock); - const component = mountWithIntl( - - - - ); + renderWithTableContext(, contextMock); - const checkBox = findTestSubject(component, 'selectAllDocsOnPageToggle'); - expect(checkBox.props().checked).toBeTruthy(); + expect(screen.getByRole('checkbox', { name: 'Deselect all visible rows' })).toBeChecked(); }); }); describe('SelectButton', () => { - test('is not checked', () => { + it('is not checked', () => { const contextMock = { ...dataTableContextMock, }; - const component = mountWithIntl( - - - + renderWithTableContext( + , + contextMock ); - const checkBox = findTestSubject(component, 'dscGridSelectDoc-i::1::'); - expect(checkBox.props().checked).toBeFalsy(); + expect(screen.getByRole('checkbox', { name: "Select document '1'" })).not.toBeChecked(); }); - test('is checked correctly', () => { + it('is checked correctly', () => { const contextMock = { ...dataTableContextMock, selectedDocsState: buildSelectedDocsState(['i::1::']), }; - const component1 = mountWithIntl( - - - + const { unmount } = renderWithTableContext( + , + contextMock ); - const checkBox1 = findTestSubject(component1, 'dscGridSelectDoc-i::1::'); - expect(checkBox1.props().checked).toBeTruthy(); - - const component2 = mountWithIntl( - - - + expect(screen.getByRole('checkbox', { name: "Select document '1'" })).toBeChecked(); + unmount(); + + renderWithTableContext( + , + contextMock ); - const checkBox2 = findTestSubject(component2, 'dscGridSelectDoc-i::2::'); - expect(checkBox2.props().checked).toBeFalsy(); + expect(screen.getByRole('checkbox', { name: "Select document '2'" })).not.toBeChecked(); }); - test('adding a selection', () => { + it('adding a selection', async () => { const contextMock = { ...dataTableContextMock, }; - const component = mountWithIntl( - - - + renderWithTableContext( + , + contextMock ); - const checkBox = findTestSubject(component, 'dscGridSelectDoc-i::1::'); - checkBox.simulate('change'); + await userEvent.click(screen.getByRole('checkbox', { name: "Select document '1'" })); + expect(contextMock.selectedDocsState.toggleDocSelection).toHaveBeenCalledWith('i::1::'); }); - test('removing a selection', () => { + it('removing a selection', async () => { const contextMock = { ...dataTableContextMock, selectedDocsState: buildSelectedDocsState(['i::1::']), }; - const component = mountWithIntl( - - - + renderWithTableContext( + , + contextMock ); - const checkBox = findTestSubject(component, 'dscGridSelectDoc-i::1::'); - checkBox.simulate('change'); + await userEvent.click(screen.getByRole('checkbox', { name: "Select document '1'" })); + expect(contextMock.selectedDocsState.toggleDocSelection).toHaveBeenCalledWith('i::1::'); }); }); describe('DataTableDocumentToolbarBtn', () => { - test('it renders the button and its menu correctly', () => { + it('it renders the button and its menu correctly', async () => { const props = { - isPlainRecord: false, - isFilterActive: false, - rows: dataTableContextRowsMock, - selectedDocsState: buildSelectedDocsState(['i::1::', 'i::2::']), - setIsFilterActive: jest.fn(), + columns: ['test'], enableComparisonMode: true, - setIsCompareActive: jest.fn(), fieldFormats: servicesMock.fieldFormats, + isFilterActive: false, + isPlainRecord: false, pageIndex: 0, pageSize: 2, + rows: dataTableContextRowsMock, + selectedDocsState: buildSelectedDocsState(['i::1::', 'i::2::']), + setIsCompareActive: jest.fn(), + setIsFilterActive: jest.fn(), toastNotifications: servicesMock.toastNotifications, - columns: ['test'], }; const contextMock = { ...dataTableContextMock, selectedDocsState: props.selectedDocsState, }; - const component = mountWithIntl( - - - - ); - const button = findTestSubject(component, 'unifiedDataTableSelectionBtn'); - expect(button.length).toBe(1); - expect(button.text()).toBe('Selected2'); - act(() => { - button.simulate('click'); - }); + renderWithTableContext(, contextMock); + + const button = getSelectionToolbarButton(2); + expect(button).toBeVisible(); - component.update(); + await userEvent.click(button); - expect(findTestSubject(component, 'dscGridShowSelectedDocuments').length).toBe(1); - expect(findTestSubject(component, 'unifiedDataTableCompareSelectedDocuments').length).toBe(1); - expect(findTestSubject(component, 'dscGridSelectAllDocs').text()).toBe('Select all 5'); + expect(screen.getByText('Show selected documents only')).toBeVisible(); + expect(screen.getByText('Compare selected')).toBeVisible(); + expect(screen.getByText('Select all 5')).toBeVisible(); - act(() => { - findTestSubject(component, 'dscGridClearSelectedDocuments').simulate('click'); - }); + await userEvent.click(screen.getByText('Clear selection')); expect(props.selectedDocsState.clearAllSelectedDocs).toHaveBeenCalled(); }); - test('filters custom bulk actions based on the available predicate', () => { + it('filters custom bulk actions based on the available predicate', async () => { const props = { - isPlainRecord: false, - isFilterActive: false, - rows: dataTableContextRowsMock, - selectedDocsState: buildSelectedDocsState(['i::1::', 'i::2::']), - setIsFilterActive: jest.fn(), - enableComparisonMode: false, - setIsCompareActive: jest.fn(), - fieldFormats: servicesMock.fieldFormats, - pageIndex: 0, - pageSize: 2, - toastNotifications: servicesMock.toastNotifications, columns: ['test'], customBulkActions: [ { @@ -282,193 +254,181 @@ describe('document selection', () => { selectedDocIds.length >= 2, }, ], + enableComparisonMode: false, + fieldFormats: servicesMock.fieldFormats, + isFilterActive: false, + isPlainRecord: false, + pageIndex: 0, + pageSize: 2, + rows: dataTableContextRowsMock, + selectedDocsState: buildSelectedDocsState(['i::1::', 'i::2::']), + setIsCompareActive: jest.fn(), + setIsFilterActive: jest.fn(), + toastNotifications: servicesMock.toastNotifications, }; const contextMock = { ...dataTableContextMock, selectedDocsState: props.selectedDocsState, }; - const component = mountWithIntl( - - - - ); - act(() => { - findTestSubject(component, 'unifiedDataTableSelectionBtn').simulate('click'); - }); - component.update(); + renderWithTableContext(, contextMock); + await userEvent.click(getSelectionToolbarButton(2)); - expect(findTestSubject(component, 'bulkActionAlways').exists()).toBe(true); - expect(findTestSubject(component, 'bulkActionNever').exists()).toBe(false); - expect(findTestSubject(component, 'bulkActionWhenTwo').exists()).toBe(true); + expect(screen.getByTestId('unifiedDataTableSelectionMenu')).toBeInTheDocument(); + expect(screen.getByText('Always')).toBeInTheDocument(); + expect(screen.queryByText('Never')).not.toBeInTheDocument(); + expect(screen.getByText('When two')).toBeInTheDocument(); }); - test('it should not render "Select all X" button if less than pageSize is selected', () => { + it('it should not render "Select all X" button if less than pageSize is selected', () => { const props = { - isPlainRecord: false, - isFilterActive: false, - rows: dataTableContextRowsMock, - selectedDocsState: buildSelectedDocsState(['i::1::']), - setIsFilterActive: jest.fn(), + columns: ['test'], enableComparisonMode: true, - setIsCompareActive: jest.fn(), fieldFormats: servicesMock.fieldFormats, + isFilterActive: false, + isPlainRecord: false, pageIndex: 0, pageSize: 2, + rows: dataTableContextRowsMock, + selectedDocsState: buildSelectedDocsState(['i::1::']), + setIsCompareActive: jest.fn(), + setIsFilterActive: jest.fn(), toastNotifications: servicesMock.toastNotifications, - columns: ['test'], }; const contextMock = { ...dataTableContextMock, selectedDocsState: props.selectedDocsState, }; - const component = mountWithIntl( - - - - ); - expect(findTestSubject(component, 'unifiedDataTableSelectionBtn').text()).toBe('Selected1'); - expect(findTestSubject(component, 'dscGridSelectAllDocs').exists()).toBe(false); + renderWithTableContext(, contextMock); + + expect(getSelectionToolbarButton(1)).toBeVisible(); + expect(screen.queryByText('Select all 1')).not.toBeInTheDocument(); }); - test('it should render "Select all X" button if all rows on the page are selected', () => { + it('it should render "Select all X" button if all rows on the page are selected', async () => { const props = { - isPlainRecord: false, - isFilterActive: false, - rows: dataTableContextRowsMock, - selectedDocsState: buildSelectedDocsState(['i::1::', 'i::2::']), - setIsFilterActive: jest.fn(), + columns: ['test'], enableComparisonMode: true, - setIsCompareActive: jest.fn(), fieldFormats: servicesMock.fieldFormats, + isFilterActive: false, + isPlainRecord: false, pageIndex: 0, pageSize: 2, + rows: dataTableContextRowsMock, + selectedDocsState: buildSelectedDocsState(['i::1::', 'i::2::']), + setIsCompareActive: jest.fn(), + setIsFilterActive: jest.fn(), toastNotifications: servicesMock.toastNotifications, - columns: ['test'], }; const contextMock = { ...dataTableContextMock, selectedDocsState: props.selectedDocsState, }; - const component = mountWithIntl( - - - - ); - expect(findTestSubject(component, 'unifiedDataTableSelectionBtn').text()).toBe('Selected2'); - const button = findTestSubject(component, 'dscGridSelectAllDocs'); - expect(button.exists()).toBe(true); + renderWithTableContext(, contextMock); + + expect(getSelectionToolbarButton(2)).toBeVisible(); - act(() => { - button.simulate('click'); - }); + const button = screen.getByText('Select all 5'); + expect(button).toBeVisible(); + + await userEvent.click(button); expect(props.selectedDocsState.selectAllDocs).toHaveBeenCalled(); }); - test('it should render "Select all X" button even if on another page', () => { + it('it should render "Select all X" button even if on another page', () => { const props = { - isPlainRecord: false, - isFilterActive: false, - rows: dataTableContextRowsMock, - selectedDocsState: buildSelectedDocsState(['i::1::', 'i::2::']), - setIsFilterActive: jest.fn(), + columns: ['test'], enableComparisonMode: true, - setIsCompareActive: jest.fn(), fieldFormats: servicesMock.fieldFormats, + isFilterActive: false, + isPlainRecord: false, pageIndex: 1, pageSize: 2, + rows: dataTableContextRowsMock, + selectedDocsState: buildSelectedDocsState(['i::1::', 'i::2::']), + setIsCompareActive: jest.fn(), + setIsFilterActive: jest.fn(), toastNotifications: servicesMock.toastNotifications, - columns: ['test'], }; const contextMock = { ...dataTableContextMock, selectedDocsState: props.selectedDocsState, }; - const component = mountWithIntl( - - - - ); - expect(findTestSubject(component, 'unifiedDataTableSelectionBtn').text()).toBe('Selected2'); - expect(findTestSubject(component, 'dscGridSelectAllDocs').exists()).toBe(true); + renderWithTableContext(, contextMock); + + expect(getSelectionToolbarButton(2)).toBeVisible(); + expect(screen.getByText('Select all 5')).toBeVisible(); }); - test('it should not render "Select all X" button if all rows are selected', () => { + it('it should not render "Select all X" button if all rows are selected', () => { const props = { - isPlainRecord: false, - isFilterActive: false, - rows: dataTableContextRowsMock, - selectedDocsState: buildSelectedDocsState(dataTableContextRowsMock.map((row) => row.id)), - setIsFilterActive: jest.fn(), + columns: ['test'], enableComparisonMode: true, - setIsCompareActive: jest.fn(), fieldFormats: servicesMock.fieldFormats, + isFilterActive: false, + isPlainRecord: false, pageIndex: 1, pageSize: 2, + rows: dataTableContextRowsMock, + selectedDocsState: buildSelectedDocsState(dataTableContextRowsMock.map((row) => row.id)), + setIsCompareActive: jest.fn(), + setIsFilterActive: jest.fn(), toastNotifications: servicesMock.toastNotifications, - columns: ['test'], }; const contextMock = { ...dataTableContextMock, selectedDocsState: props.selectedDocsState, }; - const component = mountWithIntl( - - - - ); - expect(findTestSubject(component, 'unifiedDataTableSelectionBtn').text()).toBe( - `Selected${dataTableContextRowsMock.length}` - ); - expect(findTestSubject(component, 'dscGridSelectAllDocs').exists()).toBe(false); + renderWithTableContext(, contextMock); + + expect(getSelectionToolbarButton(dataTableContextRowsMock.length)).toBeVisible(); + expect(screen.queryByText('Select all 5')).not.toBeInTheDocument(); }); }); describe('DataTableCompareToolbarBtn', () => { const props = { - isPlainRecord: false, - isFilterActive: false, - rows: dataTableContextRowsMock, - selectedDocsState: buildSelectedDocsState([]), - setIsFilterActive: jest.fn(), + columns: ['test'], enableComparisonMode: true, - setIsCompareActive: jest.fn(), fieldFormats: servicesMock.fieldFormats, + isFilterActive: false, + isPlainRecord: false, pageIndex: 0, pageSize: 2, + rows: dataTableContextRowsMock, + selectedDocsState: buildSelectedDocsState([]), + setIsCompareActive: jest.fn(), + setIsFilterActive: jest.fn(), toastNotifications: servicesMock.toastNotifications, - columns: ['test'], }; const renderCompareBtn = ({ selectedDocIds = ['1', '2'], setIsCompareActive = jest.fn(), }: Partial[0]> = {}) => { - render( - - - - - + renderWithTableContext( + , + { + ...dataTableContextMock, + selectedDocsState: props.selectedDocsState, + } ); + return { getButton: async () => { - const menuButton = await screen.findByTestId('unifiedDataTableSelectionBtn'); + const menuButton = await screen.findByRole('button', { name: /Selected/ }); + await userEvent.click(menuButton); + return screen.queryByRole('button', { name: /Compare/ }); }, }; @@ -476,27 +436,36 @@ describe('document selection', () => { it('should render the compare button', async () => { const { getButton } = renderCompareBtn(); + expect(await getButton()).toBeInTheDocument(); }); it('should call setIsCompareActive when the button is clicked', async () => { const setIsCompareActive = jest.fn(); + const { getButton } = renderCompareBtn({ setIsCompareActive }); + const button = await getButton(); expect(button).toBeInTheDocument(); - expect(button?.getAttribute('disabled')).toBeNull(); - button?.click(); + expect(button).not.toBeDisabled(); + + await userEvent.click(button!); + expect(setIsCompareActive).toHaveBeenCalledWith(true); }); it('should disable the button if limit is reached', async () => { const selectedDocIds = Array.from({ length: 500 }, (_, i) => i.toString()); const setIsCompareActive = jest.fn(); + const { getButton } = renderCompareBtn({ selectedDocIds, setIsCompareActive }); + const button = await getButton(); expect(button).toBeInTheDocument(); - expect(button?.getAttribute('disabled')).toBe(''); - button?.click(); + expect(button).toBeDisabled(); + + await userEvent.click(button!); + expect(setIsCompareActive).not.toHaveBeenCalled(); }); }); diff --git a/src/platform/packages/shared/kbn-unified-data-table/src/components/data_table_expand_button.test.tsx b/src/platform/packages/shared/kbn-unified-data-table/src/components/data_table_expand_button.test.tsx index 4da41705efe9a..7f7b5899dbae9 100644 --- a/src/platform/packages/shared/kbn-unified-data-table/src/components/data_table_expand_button.test.tsx +++ b/src/platform/packages/shared/kbn-unified-data-table/src/components/data_table_expand_button.test.tsx @@ -8,56 +8,58 @@ */ import React from 'react'; -import { mountWithIntl } from '@kbn/test-jest-helpers'; -import { findTestSubject } from '@elastic/eui/lib/test'; +import userEvent from '@testing-library/user-event'; +import { dataTableContextMock } from '../../__mocks__/table_context'; import { ExpandButton } from './data_table_expand_button'; +import { renderWithI18n } from '@kbn/test-jest-helpers'; +import { screen } from '@testing-library/react'; import { UnifiedDataTableContext } from '../table_context'; -import { dataTableContextMock } from '../../__mocks__/table_context'; -describe('Data table view button ', function () { +describe('Data table view button ', () => { it('when no document is expanded, setExpanded is called with current document', async () => { - const contextMock = { - ...dataTableContextMock, - }; - - const component = mountWithIntl( - + renderWithI18n( + ); - const button = findTestSubject(component, 'docTableExpandToggleColumn'); - await button.simulate('click'); - expect(contextMock.setExpanded).toHaveBeenCalledWith(dataTableContextMock.getRowByIndex(0)); + + await userEvent.click(screen.getByTestId('docTableExpandToggleColumn')); + + expect(dataTableContextMock.setExpanded).toHaveBeenCalledWith( + dataTableContextMock.getRowByIndex(0) + ); }); + it('when the current document is expanded, setExpanded is called with undefined', async () => { const contextMock = { ...dataTableContextMock, expanded: dataTableContextMock.getRowByIndex(0), }; - const component = mountWithIntl( + renderWithI18n( ); - const button = findTestSubject(component, 'docTableExpandToggleColumn'); - await button.simulate('click'); + + await userEvent.click(screen.getByTestId('docTableExpandToggleColumn')); + expect(contextMock.setExpanded).toHaveBeenCalledWith(undefined); }); it('when another document is expanded, setExpanded is called with the current document', async () => { @@ -66,21 +68,22 @@ describe('Data table view button ', function () { expanded: dataTableContextMock.getRowByIndex(0), }; - const component = mountWithIntl( + renderWithI18n( ); - const button = findTestSubject(component, 'docTableExpandToggleColumn'); - await button.simulate('click'); + + await userEvent.click(screen.getByTestId('docTableExpandToggleColumn')); + expect(contextMock.setExpanded).toHaveBeenCalledWith(dataTableContextMock.getRowByIndex(1)); }); }); diff --git a/src/platform/packages/shared/kbn-unified-data-table/src/components/data_table_footer.test.tsx b/src/platform/packages/shared/kbn-unified-data-table/src/components/data_table_footer.test.tsx index ba16e817c7984..d7dfc5e979f9f 100644 --- a/src/platform/packages/shared/kbn-unified-data-table/src/components/data_table_footer.test.tsx +++ b/src/platform/packages/shared/kbn-unified-data-table/src/components/data_table_footer.test.tsx @@ -8,96 +8,102 @@ */ import React from 'react'; -import { mountWithIntl } from '@kbn/test-jest-helpers'; -import { findTestSubject } from '@elastic/eui/lib/test'; -import { UnifiedDataTableFooter } from './data_table_footer'; -import { servicesMock } from '../../__mocks__/services'; -import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; +import userEvent from '@testing-library/user-event'; import { DEFAULT_PAGINATION_MODE } from '../..'; +import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; +import { renderWithI18n } from '@kbn/test-jest-helpers'; +import { screen } from '@testing-library/react'; +import { servicesMock } from '../../__mocks__/services'; +import { UnifiedDataTableFooter } from './data_table_footer'; -describe('UnifiedDataTableFooter', function () { +describe('UnifiedDataTableFooter', () => { it('should not render anything when not on the last page', async () => { - const component = mountWithIntl( + const { container } = renderWithI18n( ); - expect(component.isEmptyRender()).toBe(true); + + expect(container).toBeEmptyDOMElement(); }); it('should not render anything yet when all rows shown', async () => { - const component = mountWithIntl( + const { container } = renderWithI18n( ); - expect(component.isEmptyRender()).toBe(true); + + expect(container).toBeEmptyDOMElement(); }); it('should render a message for the last page', async () => { - const component = mountWithIntl( + renderWithI18n( ); - expect(findTestSubject(component, 'unifiedDataTableFooter').text()).toBe( - 'Search results are limited to 500 documents. Add more search terms to narrow your search.' - ); - expect(findTestSubject(component, 'dscGridSampleSizeFetchMoreLink').exists()).toBe(false); + + expect( + screen.getByText( + 'Search results are limited to 500 documents. Add more search terms to narrow your search.' + ) + ).toBeVisible(); + + expect(screen.queryByTestId('dscGridSampleSizeFetchMoreLink')).not.toBeInTheDocument(); }); it('should render a message and the button for the last page', async () => { const mockLoadMore = jest.fn(); - const component = mountWithIntl( + renderWithI18n( ); - expect(findTestSubject(component, 'unifiedDataTableFooter').text()).toBe( - 'Search results are limited to 500 documents.Load more' - ); - const button = findTestSubject(component, 'dscGridSampleSizeFetchMoreLink'); - expect(button.exists()).toBe(true); + expect(screen.getByText('Search results are limited to 500 documents.')).toBeVisible(); + + const button = screen.getByText('Load more'); + expect(button).toBeVisible(); - button.simulate('click'); + await userEvent.click(button); expect(mockLoadMore).toHaveBeenCalledTimes(1); }); @@ -105,94 +111,94 @@ describe('UnifiedDataTableFooter', function () { it('should render the load more button where pagination mode is set to singlePage and user has reached the bottom of the page', () => { const mockLoadMore = jest.fn(); - const component = mountWithIntl( + renderWithI18n( ); - expect(findTestSubject(component, 'dscGridSampleSizeFetchMoreLink').exists()).toBe(true); + expect(screen.getByText('Search results are limited to 500 documents.')).toBeVisible(); + expect(screen.getByText('Load more')).toBeVisible(); }); it('should not render the load more button where pagination mode is set to singlePage and but the user has not reached the bottom of the page', () => { const mockLoadMore = jest.fn(); - const component = mountWithIntl( + renderWithI18n( ); - expect(findTestSubject(component, 'dscGridSampleSizeFetchMoreLink').exists()).toBe(false); + expect(screen.queryByTestId('dscGridSampleSizeFetchMoreLink')).not.toBeInTheDocument(); }); it('should render a disabled button when loading more', async () => { const mockLoadMore = jest.fn(); - const component = mountWithIntl( + renderWithI18n( ); - expect(findTestSubject(component, 'unifiedDataTableFooter').text()).toBe( - 'Search results are limited to 500 documents.Load more' - ); - - const button = findTestSubject(component, 'dscGridSampleSizeFetchMoreLink'); - expect(button.exists()).toBe(true); - expect(button.prop('disabled')).toBe(true); + expect(screen.getByText('Search results are limited to 500 documents.')).toBeVisible(); - button.simulate('click'); + const button = screen.getByRole('button', { name: 'Load more' }); + expect(button).toBeVisible(); + expect(button).toBeDisabled(); expect(mockLoadMore).not.toHaveBeenCalled(); }); it('should render a message when max total limit is reached', async () => { - const component = mountWithIntl( + renderWithI18n( ); - expect(findTestSubject(component, 'unifiedDataTableFooter').text()).toBe( - 'Search results are limited to 10000 documents. Add more search terms to narrow your search.' - ); + + expect( + screen.getByText( + 'Search results are limited to 10000 documents. Add more search terms to narrow your search.' + ) + ).toBeVisible(); }); }); diff --git a/src/platform/packages/shared/kbn-unified-data-table/src/components/data_table_summary_column_header.test.tsx b/src/platform/packages/shared/kbn-unified-data-table/src/components/data_table_summary_column_header.test.tsx index 07884881078ea..40b98051f952a 100644 --- a/src/platform/packages/shared/kbn-unified-data-table/src/components/data_table_summary_column_header.test.tsx +++ b/src/platform/packages/shared/kbn-unified-data-table/src/components/data_table_summary_column_header.test.tsx @@ -7,40 +7,28 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import type { ReactElement } from 'react'; import React from 'react'; -import { mountWithI18nProvider } from '@kbn/test-jest-helpers'; +import userEvent from '@testing-library/user-event'; +import { renderWithKibanaRenderContext } from '@kbn/test-jest-helpers'; +import { screen, within } from '@testing-library/react'; import { UnifiedDataTableSummaryColumnHeader } from './data_table_summary_column_header'; -import { EuiIconTip } from '@elastic/eui'; -import ColumnHeaderTruncateContainer from './column_header_truncate_container'; -import { i18n } from '@kbn/i18n'; describe('UnifiedDataTableSummaryColumnHeader', () => { - async function mountComponent(element: ReactElement) { - const component = mountWithI18nProvider(element); - // Wait a tick for lazy modules or portals inside EuiIconTip - await new Promise((r) => setTimeout(r, 5)); - component.update(); - return component; - } - - it('renders column name and default tooltip icon', async () => { - const component = await mountComponent( + it('renders column name and tooltip icon', () => { + renderWithKibanaRenderContext( ); - expect(component.text()).toContain('My Column'); - expect(component.find(EuiIconTip).exists()).toBe(true); - // Default subject attribute is important for functional tests - expect(component.find('[data-test-subj="unifiedDataTable_headerSummaryIcon"]').exists()).toBe( - true - ); + const column = screen.getByText('My Column'); + expect(column).toBeVisible(); + expect(within(column).getByText('Info')).toBeVisible(); }); - it('applies custom tooltip content and title when provided', async () => { + it('shows custom tooltip content and title when provided', async () => { const customContent = 'Custom tooltip'; const customTitle = 'Custom title'; - const component = await mountComponent( + + renderWithKibanaRenderContext( { /> ); - const icon = component.find(EuiIconTip).first(); - expect(icon.prop('content')).toBe(customContent); - expect(icon.prop('title')).toBe(customTitle); - }); - - it('renders default column name, tooltip, and passes props correctly when no overrides', async () => { - const component = await mountComponent(); - - // Default column name (i18n translated "Summary") should be present - expect(component.text()).toContain( - i18n.translate('unifiedDataTable.tableHeader.summary', { defaultMessage: 'Summary' }) - ); + const icon = screen.getByText('Info'); + await userEvent.hover(icon); - const icon = component.find(EuiIconTip).first(); - // Default tooltip content & title - expect(icon.prop('content')).toBe( - i18n.translate('unifiedDataTable.tableHeader.sourceFieldIconTooltip', { - defaultMessage: 'Shows a quick view of the record using its key:value pairs.', - }) - ); - expect(icon.prop('title')).toBe( - i18n.translate('unifiedDataTable.tableHeader.sourceFieldIconTooltipTitle', { - defaultMessage: 'Summary', - }) - ); - - // Default data-test-subj should exist - expect(component.find('[data-test-subj="unifiedDataTable_headerSummaryIcon"]').exists()).toBe( - true - ); + expect(await screen.findByText(customTitle)).toBeVisible(); + expect(screen.getByText(customContent)).toBeVisible(); }); - it('passes headerRowHeight and custom data-test-subj props', async () => { - const component = await mountComponent( - - ); + it('renders default column name and tooltip', async () => { + renderWithKibanaRenderContext(); + + expect(screen.getByText('Summary')).toBeVisible(); - // ColumnHeaderTruncateContainer should receive the headerRowHeight prop - expect(component.find(ColumnHeaderTruncateContainer).prop('headerRowHeight')).toBe(3); + const icon = screen.getByText('Info'); + await userEvent.hover(icon); - // Custom data-test-subj should be applied - expect(component.find('[data-test-subj="customTipSubj"]').exists()).toBe(true); + expect(await screen.findAllByText('Summary')).toHaveLength(2); + expect( + screen.getByText('Shows a quick view of the record using its key:value pairs.') + ).toBeVisible(); }); }); diff --git a/src/platform/packages/shared/kbn-unified-data-table/src/components/default_cell_actions.test.tsx b/src/platform/packages/shared/kbn-unified-data-table/src/components/default_cell_actions.test.tsx index 22e5a92064261..176d18ac4231e 100644 --- a/src/platform/packages/shared/kbn-unified-data-table/src/components/default_cell_actions.test.tsx +++ b/src/platform/packages/shared/kbn-unified-data-table/src/components/default_cell_actions.test.tsx @@ -7,192 +7,215 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -const mockCopyToClipboard = jest.fn((value) => true); -jest.mock('@elastic/eui', () => { - const original = jest.requireActual('@elastic/eui'); - return { - ...original, - copyToClipboard: (value: string) => mockCopyToClipboard(value), - }; -}); - +import type { EuiDataGridColumnCellActionProps } from '@elastic/eui'; +import type { DataViewField } from '@kbn/data-views-plugin/public'; import React from 'react'; -import { mountWithIntl } from '@kbn/test-jest-helpers'; -import { findTestSubject } from '@elastic/eui/lib/test'; +import userEvent from '@testing-library/user-event'; import { - FilterInBtn, - FilterOutBtn, buildCellActions, buildCopyValueButton, + FilterInBtn, + FilterOutBtn, } from './default_cell_actions'; +import { dataTableContextMock } from '../../__mocks__/table_context'; +import { EuiButtonEmpty } from '@elastic/eui'; +import { renderWithI18n } from '@kbn/test-jest-helpers'; +import { screen } from '@testing-library/react'; import { servicesMock } from '../../__mocks__/services'; import { UnifiedDataTableContext } from '../table_context'; -import type { EuiDataGridColumnCellActionProps } from '@elastic/eui'; -import { EuiButton } from '@elastic/eui'; -import { dataTableContextMock } from '../../__mocks__/table_context'; -import type { DataViewField } from '@kbn/data-views-plugin/public'; -describe('Default cell actions ', function () { +const TestCellActionButton: EuiDataGridColumnCellActionProps['Component'] = EuiButtonEmpty; + +const createCellActionProps = ( + props: Partial +): EuiDataGridColumnCellActionProps => ({ + colIndex: 0, + columnId: 'extension', + Component: TestCellActionButton, + isExpanded: false, + rowIndex: 0, + ...props, +}); + +const getField = (fieldName: string): DataViewField => { + const field = dataTableContextMock.dataView.getFieldByName(fieldName); + + if (!field) throw new Error(`Missing test field "${fieldName}"`); + + return field; +}; + +describe('Default cell actions ', () => { + const execCommandMock = (global.document.execCommand = jest.fn()); + + beforeEach(() => { + jest.clearAllMocks(); + }); + const CopyBtn = buildCopyValueButton( - { - Component: () => <>, + createCellActionProps({ colIndex: 0, columnId: 'extension', - } as unknown as EuiDataGridColumnCellActionProps, + }), servicesMock.toastNotifications, dataTableContextMock.valueToStringConverter ); + const extensionField = getField('extension'); + const messageField = getField('message'); + const sourceField = getField('_source'); - it('should not show cell actions for unfilterable fields', async () => { + it('should not show cell actions for unfilterable fields', () => { const cellActions = buildCellActions( - { name: 'foo', filterable: false } as DataViewField, + messageField, servicesMock.toastNotifications, dataTableContextMock.valueToStringConverter ); + expect(cellActions.length).toEqual(1); expect( - cellActions[0]({ - Component: () => <>, - colIndex: 1, - columnId: 'extension', - } as unknown as EuiDataGridColumnCellActionProps).props['aria-label'] + cellActions[0]( + createCellActionProps({ + colIndex: 1, + columnId: 'extension', + }) + ).props['aria-label'] ).toEqual(CopyBtn.props['aria-label']); }); - it('should show filter actions for filterable fields', async () => { + it('should show filter actions for filterable fields', () => { const cellActions = buildCellActions( - { name: 'foo', filterable: true } as DataViewField, + extensionField, servicesMock.toastNotifications, dataTableContextMock.valueToStringConverter, jest.fn() ); + expect(cellActions).toHaveLength(3); }); - it('should show Copy action for _source field', async () => { + it('should show Copy action for _source field', () => { const cellActions = buildCellActions( - { name: '_source', type: '_source', filterable: false } as DataViewField, + sourceField, servicesMock.toastNotifications, dataTableContextMock.valueToStringConverter ); + expect( - cellActions[0]({ - Component: () => <>, - colIndex: 1, - columnId: 'extension', - } as unknown as EuiDataGridColumnCellActionProps).props['aria-label'] + cellActions[0]( + createCellActionProps({ + colIndex: 1, + columnId: 'extension', + }) + ).props['aria-label'] ).toEqual(CopyBtn.props['aria-label']); }); it('triggers filter function when FilterInBtn is clicked', async () => { - const component = mountWithIntl( + renderWithI18n( , + Component: TestCellActionButton, rowIndex: 1, colIndex: 1, columnId: 'extension', isExpanded: false, }} - field={{ name: 'extension', filterable: true } as DataViewField} + field={extensionField} /> ); - const button = findTestSubject(component, 'filterForButton'); - await button.simulate('click'); - expect(dataTableContextMock.onFilter).toHaveBeenCalledWith( - { name: 'extension', filterable: true }, - 'jpg', - '+' - ); + + await userEvent.click(screen.getByRole('button', { name: 'Filter for this extension' })); + + expect(dataTableContextMock.onFilter).toHaveBeenCalledWith(extensionField, 'jpg', '+'); }); + it('triggers filter function when FilterInBtn is clicked for a non-provided value', async () => { - const component = mountWithIntl( + renderWithI18n( , - rowIndex: 0, colIndex: 1, columnId: 'extension', + Component: TestCellActionButton, isExpanded: false, + rowIndex: 0, }} - field={{ name: 'extension', filterable: true } as DataViewField} + field={extensionField} /> ); - const button = findTestSubject(component, 'filterForButton'); - await button.simulate('click'); - expect(dataTableContextMock.onFilter).toHaveBeenCalledWith( - { name: 'extension', filterable: true }, - undefined, - '+' - ); + + await userEvent.click(screen.getByRole('button', { name: 'Filter for this extension' })); + + expect(dataTableContextMock.onFilter).toHaveBeenCalledWith(extensionField, undefined, '+'); }); + it('triggers filter function when FilterInBtn is clicked for an empty string value', async () => { - const component = mountWithIntl( + renderWithI18n( , - rowIndex: 4, colIndex: 1, columnId: 'message', + Component: TestCellActionButton, isExpanded: false, + rowIndex: 4, }} - field={{ name: 'message', filterable: true } as DataViewField} + field={messageField} /> ); - const button = findTestSubject(component, 'filterForButton'); - await button.simulate('click'); - expect(dataTableContextMock.onFilter).toHaveBeenCalledWith( - { name: 'message', filterable: true }, - '', - '+' - ); + + await userEvent.click(screen.getByRole('button', { name: 'Filter for this message' })); + + expect(dataTableContextMock.onFilter).toHaveBeenCalledWith(messageField, '', '+'); }); + it('triggers filter function when FilterOutBtn is clicked', async () => { - const component = mountWithIntl( + renderWithI18n( , - rowIndex: 1, colIndex: 1, columnId: 'extension', + Component: TestCellActionButton, isExpanded: false, + rowIndex: 1, }} - field={{ name: 'extension', filterable: true } as DataViewField} + field={extensionField} /> ); - const button = findTestSubject(component, 'filterOutButton'); - await button.simulate('click'); - expect(dataTableContextMock.onFilter).toHaveBeenCalledWith( - { name: 'extension', filterable: true }, - 'jpg', - '-' - ); + + await userEvent.click(screen.getByRole('button', { name: 'Filter out this extension' })); + + expect(dataTableContextMock.onFilter).toHaveBeenCalledWith(extensionField, 'jpg', '-'); }); + it('triggers clipboard copy when CopyBtn is clicked', async () => { - const component = mountWithIntl( + execCommandMock.mockImplementationOnce(() => true); + + renderWithI18n( {buildCopyValueButton( - { - Component: (props: any) => , + createCellActionProps({ colIndex: 1, - rowIndex: 1, columnId: 'extension', - } as unknown as EuiDataGridColumnCellActionProps, + rowIndex: 1, + }), servicesMock.toastNotifications, dataTableContextMock.valueToStringConverter )} ); - const button = findTestSubject(component, 'copyClipboardButton'); - await button.simulate('click'); - expect(mockCopyToClipboard).toHaveBeenCalledWith('jpg'); + + await userEvent.click(screen.getByRole('button', { name: 'Copy value of extension' })); + + expect(execCommandMock).toHaveBeenCalledWith('copy'); + expect(servicesMock.toastNotifications.addInfo).toHaveBeenCalledWith({ + title: 'Copied to clipboard', + }); }); }); diff --git a/src/platform/packages/shared/kbn-unified-data-table/src/components/json_code_editor/__snapshots__/json_code_editor.test.tsx.snap b/src/platform/packages/shared/kbn-unified-data-table/src/components/json_code_editor/__snapshots__/json_code_editor.test.tsx.snap deleted file mode 100644 index 7af546298e0d8..0000000000000 --- a/src/platform/packages/shared/kbn-unified-data-table/src/components/json_code_editor/__snapshots__/json_code_editor.test.tsx.snap +++ /dev/null @@ -1,17 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`returns the \`JsonCodeEditor\` component 1`] = ` - -`; diff --git a/src/platform/packages/shared/kbn-unified-data-table/src/components/json_code_editor/json_code_editor.test.tsx b/src/platform/packages/shared/kbn-unified-data-table/src/components/json_code_editor/json_code_editor.test.tsx index d51eddb320126..348a0d3be1773 100644 --- a/src/platform/packages/shared/kbn-unified-data-table/src/components/json_code_editor/json_code_editor.test.tsx +++ b/src/platform/packages/shared/kbn-unified-data-table/src/components/json_code_editor/json_code_editor.test.tsx @@ -7,17 +7,33 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import React from 'react'; -import { shallow } from 'enzyme'; +import '@kbn/code-editor-mock/jest_helper'; import JsonCodeEditor from './json_code_editor'; +import React from 'react'; +import { render, screen } from '@testing-library/react'; + +describe('JsonCodeEditor', () => { + it('returns the `JsonCodeEditor` component', () => { + const value = { + _index: 'test', + _type: 'doc', + _id: 'foo', + _score: 1, + _source: { test: 123 }, + }; + render(); + + const editor = screen.getByLabelText('Read only JSON view of an elasticsearch document'); -it('returns the `JsonCodeEditor` component', () => { - const value = { - _index: 'test', - _type: 'doc', - _id: 'foo', - _score: 1, - _source: { test: 123 }, - }; - expect(shallow()).toMatchSnapshot(); + expect(editor).toHaveDisplayValue(`{ + "_index": "test", + "_type": "doc", + "_id": "foo", + "_score": 1, + "_source": { + "test": 123 + } + }`); + expect(screen.queryByText('Copy to clipboard')).not.toBeInTheDocument(); + }); }); diff --git a/src/platform/packages/shared/kbn-unified-data-table/src/components/source_document.test.tsx b/src/platform/packages/shared/kbn-unified-data-table/src/components/source_document.test.tsx index a2d319401f5c7..abfc091bba714 100644 --- a/src/platform/packages/shared/kbn-unified-data-table/src/components/source_document.test.tsx +++ b/src/platform/packages/shared/kbn-unified-data-table/src/components/source_document.test.tsx @@ -7,20 +7,20 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ +import type { EsHitRecord } from '@kbn/discover-utils/src/types'; +import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public'; +import React from 'react'; +import SourceDocument from './source_document'; +import { buildDataTableRecord } from '@kbn/discover-utils'; import { - dataViewMock, - createDataViewWithBytesField, columnsMetaOverridingBytesType, + createDataViewWithBytesField, createFormatFieldValueReactSpy, + dataViewMock, expectFieldCallToMatch, } from '@kbn/discover-utils/src/__mocks__'; -import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public'; -import { mountWithIntl } from '@kbn/test-jest-helpers'; -import { render } from '@testing-library/react'; -import React from 'react'; -import SourceDocument from './source_document'; -import type { EsHitRecord } from '@kbn/discover-utils/src/types'; -import { buildDataTableRecord } from '@kbn/discover-utils'; +import { renderWithI18n } from '@kbn/test-jest-helpers'; +import { screen, within } from '@testing-library/react'; const mockServices = { fieldFormats: { @@ -44,26 +44,29 @@ const rowsSource: EsHitRecord[] = [ const build = (hit: EsHitRecord) => buildDataTableRecord(hit, dataViewMock); -describe('Unified data table source document cell rendering', function () { +describe('Unified data table source document cell rendering', () => { it('renders a description list for source type documents', () => { const rows = rowsSource.map(build); - const component = mountWithIntl( + renderWithI18n( false} - maxEntries={100} isPlainRecord={true} - columnsMeta={undefined} + maxEntries={100} + row={rows[0]} + shouldShowFieldHandler={() => false} + useTopLevelObjectColumns={false} /> ); - expect(component.html()).toMatchInlineSnapshot( - `"
extension
.gz
_score
1
"` - ); + + const descriptionList = screen.getByTestId('discoverCellDescriptionList'); + expect(within(descriptionList).getByText('extension')).toBeVisible(); + expect(within(descriptionList).getByText('.gz')).toBeVisible(); + expect(within(descriptionList).getByText('_score')).toBeVisible(); + expect(within(descriptionList).getByText('1')).toBeVisible(); }); it('passes values through appropriate formatter when `useTopLevelObjectColumns` is true', () => { @@ -71,6 +74,7 @@ describe('Unified data table source document cell rendering', function () { const mockFieldFormats = { getDefaultInstance: jest.fn(() => ({ convertToReact: mockConvertToReact })), }; + const row = build({ _id: '1', _index: 'test', @@ -79,22 +83,25 @@ describe('Unified data table source document cell rendering', function () { 'foo.data': ['my foo value'], }, }); - const component = mountWithIntl( + + renderWithI18n( true} - maxEntries={100} isPlainRecord={true} - columnsMeta={undefined} + maxEntries={100} + row={row} + shouldShowFieldHandler={() => true} + useTopLevelObjectColumns={true} /> ); + const descriptionList = screen.getByTestId('discoverCellDescriptionList'); + expect(within(descriptionList).getByText('foo.data')).toBeVisible(); + expect(within(descriptionList).getByText('my bar value')).toBeVisible(); expect(mockConvertToReact).toHaveBeenCalled(); - expect(component.html()).toContain('my bar value'); }); it('renders a dash in ES|QL mode when all field values are null', () => { @@ -105,20 +112,21 @@ describe('Unified data table source document cell rendering', function () { _source: { bytes: null, extension: null }, }); - const { container } = render( + renderWithI18n( false} - maxEntries={100} isPlainRecord={true} - columnsMeta={undefined} + maxEntries={100} + row={row} + shouldShowFieldHandler={() => false} + useTopLevelObjectColumns={false} /> ); - expect(container.textContent).toBe('—'); + + expect(screen.getByText('—')).toBeVisible(); }); describe('with columnsMeta', () => { @@ -136,20 +144,25 @@ describe('Unified data table source document cell rendering', function () { testDataView ); - render( + renderWithI18n( true} - maxEntries={100} isPlainRecord={true} - columnsMeta={undefined} + maxEntries={100} + row={row} + shouldShowFieldHandler={() => true} + useTopLevelObjectColumns={false} /> ); + const descriptionList = screen.getByTestId('discoverCellDescriptionList'); + expect(within(descriptionList).getByText('bytes')).toBeVisible(); + expect(within(descriptionList).getByText('_index')).toBeVisible(); + expect(within(descriptionList).getByText('_score')).toBeVisible(); + expect(within(descriptionList).getAllByText('formatted')).toHaveLength(3); expectFieldCallToMatch(formatFieldValueReactSpy, 'bytes', 'number'); formatFieldValueReactSpy.mockRestore(); }); @@ -168,20 +181,25 @@ describe('Unified data table source document cell rendering', function () { testDataView ); - render( + renderWithI18n( true} - maxEntries={100} isPlainRecord={true} - columnsMeta={columnsMetaOverridingBytesType} + maxEntries={100} + row={row} + shouldShowFieldHandler={() => true} + useTopLevelObjectColumns={false} /> ); + const descriptionList = screen.getByTestId('discoverCellDescriptionList'); + expect(within(descriptionList).getByText('bytes')).toBeVisible(); + expect(within(descriptionList).getByText('_index')).toBeVisible(); + expect(within(descriptionList).getByText('_score')).toBeVisible(); + expect(within(descriptionList).getAllByText('formatted')).toHaveLength(3); expectFieldCallToMatch(formatFieldValueReactSpy, 'bytes', 'string', ['keyword']); formatFieldValueReactSpy.mockRestore(); }); diff --git a/src/platform/packages/shared/kbn-unified-field-list/src/components/field_list/field_list.test.tsx b/src/platform/packages/shared/kbn-unified-field-list/src/components/field_list/field_list.test.tsx index 575de10ff906e..7290f31a90d19 100644 --- a/src/platform/packages/shared/kbn-unified-field-list/src/components/field_list/field_list.test.tsx +++ b/src/platform/packages/shared/kbn-unified-field-list/src/components/field_list/field_list.test.tsx @@ -8,39 +8,47 @@ */ import React from 'react'; -import { EuiText, EuiProgress } from '@elastic/eui'; -import { mountWithIntl } from '@kbn/test-jest-helpers'; +import { EuiText } from '@elastic/eui'; import { FieldList } from './field_list'; +import { renderWithI18n } from '@kbn/test-jest-helpers'; +import { screen } from '@testing-library/react'; describe('UnifiedFieldList ', () => { - it('should render correctly when processing', async () => { - expect(mountWithIntl().find(EuiProgress)?.length).toBe(1); - expect(mountWithIntl().find(EuiProgress)?.length).toBe(0); + it('should render correctly when processing', () => { + const { unmount } = renderWithI18n(); + + expect(screen.getByTestId('fieldListLoading')).toBeVisible(); + + unmount(); + + renderWithI18n(); + + expect(screen.queryByTestId('fieldListLoading')).not.toBeInTheDocument(); }); - it('should render correctly with content', async () => { - const wrapper = mountWithIntl( + it('should render correctly with content', () => { + renderWithI18n( {'content'} ); - expect(wrapper.find(EuiText).first().text()).toBe('content'); + expect(screen.getByText('content')).toBeVisible(); }); - it('should render correctly with additional elements', async () => { - const wrapper = mountWithIntl( + it('should render correctly with additional elements', () => { + renderWithI18n( {'append'}} isProcessing={false} prepend={{'prepend'}} - append={{'append'}} > {'content'} ); - expect(wrapper.find(EuiText).first().text()).toBe('prepend'); - expect(wrapper.find(EuiText).at(1).text()).toBe('content'); - expect(wrapper.find(EuiText).at(2).text()).toBe('append'); + expect(screen.getByText('prepend')).toBeVisible(); + expect(screen.getByText('content')).toBeVisible(); + expect(screen.getByText('append')).toBeVisible(); }); }); diff --git a/src/platform/packages/shared/kbn-unified-field-list/src/components/field_stats/field_number_summary.test.tsx b/src/platform/packages/shared/kbn-unified-field-list/src/components/field_stats/field_number_summary.test.tsx index 7cb0f78df0dff..433a3fc51d730 100644 --- a/src/platform/packages/shared/kbn-unified-field-list/src/components/field_stats/field_number_summary.test.tsx +++ b/src/platform/packages/shared/kbn-unified-field-list/src/components/field_stats/field_number_summary.test.tsx @@ -9,57 +9,62 @@ import React from 'react'; import { createStubDataView } from '@kbn/data-views-plugin/common/data_views/data_view.stub'; -import { mountWithIntl } from '@kbn/test-jest-helpers'; import { FieldNumberSummary } from './field_number_summary'; +import { renderWithI18n } from '@kbn/test-jest-helpers'; +import { screen, within } from '@testing-library/react'; const dataView = createStubDataView({ spec: { - id: 'test', - title: 'test', fields: { bytes_counter: { - timeSeriesMetric: 'counter', - name: 'bytes_counter', - type: 'number', - esTypes: ['long'], aggregatable: true, - searchable: true, count: 10, + esTypes: ['long'], + isMapped: true, + name: 'bytes_counter', readFromDocValues: true, scripted: false, - isMapped: true, + searchable: true, + timeSeriesMetric: 'counter', + type: 'number', }, }, + id: 'test', + title: 'test', }, }); describe('UnifiedFieldList ', () => { - it('should render min and max correctly', async () => { - const wrapper = mountWithIntl( + it('should render min and max correctly', () => { + renderWithI18n( ); - expect(wrapper.text()).toBe('min45max12345'); + const summary = screen.getByTestId('test-subj-numberSummary'); + expect(within(summary).getByText('min')).toBeVisible(); + expect(within(summary).getByText('45')).toBeVisible(); + expect(within(summary).getByText('max')).toBeVisible(); + expect(within(summary).getByText('12345')).toBeVisible(); }); - it('should not fail if data is invalid', async () => { - const wrapper = mountWithIntl( + it('should not fail if data is invalid', () => { + renderWithI18n( ); - expect(wrapper.isEmptyRender()).toBe(true); + expect(screen.queryByTestId('test-subj-numberSummary')).not.toBeInTheDocument(); }); }); diff --git a/src/platform/packages/shared/kbn-unified-field-list/src/components/field_stats/field_top_values.test.tsx b/src/platform/packages/shared/kbn-unified-field-list/src/components/field_stats/field_top_values.test.tsx index c2c73e096e912..49c6f524bb628 100644 --- a/src/platform/packages/shared/kbn-unified-field-list/src/components/field_stats/field_top_values.test.tsx +++ b/src/platform/packages/shared/kbn-unified-field-list/src/components/field_stats/field_top_values.test.tsx @@ -7,63 +7,28 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import React from 'react'; -import { EuiProgress, EuiButtonIcon } from '@elastic/eui'; -import { mountWithIntl } from '@kbn/test-jest-helpers'; -import type { DataView } from '@kbn/data-views-plugin/common'; import type { FieldTopValuesProps } from './field_top_values'; -import { FieldTopValues } from './field_top_values'; -import type { ReactWrapper } from '@kbn/test-jest-helpers/src/testbed/types'; -import { fieldFormatsServiceMock } from '@kbn/field-formats-plugin/public/mocks'; -import { ES_FIELD_TYPES, KBN_FIELD_TYPES } from '@kbn/field-types'; +import React from 'react'; +import userEvent from '@testing-library/user-event'; +import { dataViewMock } from '@kbn/discover-utils/src/__mocks__'; import { EMPTY_LABEL } from '@kbn/field-formats-common'; - -// Similar to wrapper.text() but filtered by a selector -const getChildrenTextBySelector = (wrapper: ReactWrapper, selector: string) => { - let text = ''; - const children = wrapper.find(selector); - - children.forEach((element) => { - text += element.text(); - }); - - return text; +import { FieldTopValues } from './field_top_values'; +import { renderWithI18n } from '@kbn/test-jest-helpers'; +import { screen } from '@testing-library/react'; + +const expectTopValueProgress = (label: string, percentage: string) => { + expect(screen.getByRole('progressbar', { name: label })).toHaveAttribute( + 'aria-valuetext', + percentage + ); }; describe('UnifiedFieldList ', () => { let defaultProps: FieldTopValuesProps; - let dataView: DataView; beforeEach(() => { - dataView = { - id: '1', - title: 'my-fake-index-pattern', - timeFieldName: 'timestamp', - fields: [ - { - name: 'source', - displayName: 'source', - type: 'string', - aggregatable: true, - searchable: true, - filterable: true, - }, - ], - getFormatterForField: jest.fn(() => ({ - convertToText: jest.fn((s: unknown) => - fieldFormatsServiceMock - .createStartContract() - .getDefaultInstance(KBN_FIELD_TYPES.STRING, [ES_FIELD_TYPES.STRING]) - .convertToText(s) - ), - })), - } as unknown as DataView; - defaultProps = { areExamples: false, - dataView, - field: dataView.fields.find((f) => f.name === 'source')!, - sampledValuesCount: 5000, buckets: [ { count: 500, @@ -76,34 +41,42 @@ describe('UnifiedFieldList ', () => { ], color: '#000', 'data-test-subj': 'testing', + dataView: dataViewMock, + field: dataViewMock.getFieldByName('extension')!, + sampledValuesCount: 5000, }; }); - it('should render correctly without filter actions', async () => { - const wrapper = mountWithIntl(); - const text = getChildrenTextBySelector(wrapper, 'div.euiProgress__data'); + it('should render correctly without filter actions', () => { + renderWithI18n(); - expect(text).toBe('sourceA10.0%sourceB0.0%Other90.0%'); - expect(wrapper.find(EuiProgress)).toHaveLength(3); - expect(wrapper.find(EuiButtonIcon)).toHaveLength(0); + expectTopValueProgress('sourceA', '10.0%'); + expectTopValueProgress('sourceB', '0.0%'); + expectTopValueProgress('Other', '90.0%'); + + expect(screen.getAllByRole('progressbar')).toHaveLength(3); + expect(screen.queryAllByRole('button')).toHaveLength(0); }); it('should render correctly with filter actions', async () => { const mockAddFilter = jest.fn(); - const wrapper = mountWithIntl(); - const text = getChildrenTextBySelector(wrapper, 'div.euiProgress__data'); - expect(text).toBe('sourceA10.0%sourceB0.0%Other90.0%'); - expect(wrapper.find(EuiProgress)).toHaveLength(3); - expect(wrapper.find(EuiButtonIcon)).toHaveLength(4); + renderWithI18n(); + + expectTopValueProgress('sourceA', '10.0%'); + expectTopValueProgress('sourceB', '0.0%'); + expectTopValueProgress('Other', '90.0%'); - wrapper.find(EuiButtonIcon).first().simulate('click'); + expect(screen.getAllByRole('progressbar')).toHaveLength(3); + expect(screen.getAllByRole('button')).toHaveLength(4); + + await userEvent.click(screen.getByRole('button', { name: 'Filter for extension: "sourceA"' })); expect(mockAddFilter).toHaveBeenCalledWith(defaultProps.field, 'sourceA', '+'); }); - it('should render correctly without Other section', async () => { - const wrapper = mountWithIntl( + it('should render correctly without Other section', () => { + renderWithI18n( ', () => { ]} /> ); - const text = getChildrenTextBySelector(wrapper, 'div.euiProgress__data'); - expect(text).toBe('sourceA60.0%sourceB30.0%sourceC10.0%'); + expectTopValueProgress('sourceA', '60.0%'); + expectTopValueProgress('sourceB', '30.0%'); + expectTopValueProgress('sourceC', '10.0%'); + + expect(screen.queryByText('Other')).not.toBeInTheDocument(); }); - it('should render correctly with empty strings', async () => { - const wrapper = mountWithIntl( + it('should render correctly with empty strings', () => { + renderWithI18n( ', () => { ]} /> ); - const text = getChildrenTextBySelector(wrapper, 'div.euiProgress__data'); - expect(text).toBe(`${EMPTY_LABEL}60.0%sourceA30.0%sourceB0.4%Other9.6%`); + expectTopValueProgress(EMPTY_LABEL, '60.0%'); + expectTopValueProgress('sourceA', '30.0%'); + expectTopValueProgress('sourceB', '0.4%'); + expectTopValueProgress('Other', '9.6%'); }); - it('should render correctly without floating point', async () => { - const wrapper = mountWithIntl( + it('should render correctly without floating point', () => { + renderWithI18n( ', () => { ]} /> ); - const text = getChildrenTextBySelector(wrapper, 'div.euiProgress__data'); - expect(text).toBe('sourceA100%'); + expectTopValueProgress('sourceA', '100%'); }); }); diff --git a/src/platform/packages/shared/kbn-unified-field-list/src/containers/unified_field_list_item/field_list_item.test.tsx b/src/platform/packages/shared/kbn-unified-field-list/src/containers/unified_field_list_item/field_list_item.test.tsx index cc134db3d8fbd..50f5915cceea7 100644 --- a/src/platform/packages/shared/kbn-unified-field-list/src/containers/unified_field_list_item/field_list_item.test.tsx +++ b/src/platform/packages/shared/kbn-unified-field-list/src/containers/unified_field_list_item/field_list_item.test.tsx @@ -7,18 +7,17 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { act } from 'react-dom/test-utils'; -import { EuiButtonIcon, EuiPopover, EuiProgress, EuiThemeProvider } from '@elastic/eui'; +import type { UnifiedFieldListItemProps } from './field_list_item'; import React from 'react'; -import { findTestSubject } from '@elastic/eui/lib/test'; -import { mountWithIntl } from '@kbn/test-jest-helpers'; +import userEvent from '@testing-library/user-event'; +import { createStateService } from '../services/state_service'; import { DataViewField } from '@kbn/data-views-plugin/public'; -import { stubDataView } from '@kbn/data-views-plugin/common/data_view.stub'; +import { EuiThemeProvider } from '@elastic/eui'; import { getServicesMock } from '../../../__mocks__/services.mock'; -import type { UnifiedFieldListItemProps } from './field_list_item'; +import { renderWithKibanaRenderContext } from '@kbn/test-jest-helpers'; +import { screen, waitFor, within } from '@testing-library/react'; +import { stubDataView } from '@kbn/data-views-plugin/common/data_view.stub'; import { UnifiedFieldListItem } from './field_list_item'; -import { FieldItemButton } from '../../components/field_item_button'; -import { createStateService } from '../services/state_service'; jest.mock('../../services/field_stats', () => ({ loadFieldStats: jest.fn().mockResolvedValue({ @@ -40,29 +39,30 @@ jest.mock('../../services/field_stats', () => ({ }), })); -async function getComponent({ - selected = false, - field, +const renderComponent = async ({ canFilter = true, + field, isBreakdownSupported = true, + selected = false, }: { - selected?: boolean; - field?: DataViewField; canFilter?: boolean; + field?: DataViewField; isBreakdownSupported?: boolean; -}) { + selected?: boolean; +}) => { const finalField = field ?? new DataViewField({ - name: 'bytes', - type: 'number', - esTypes: ['long'], + aggregatable: true, count: 10, + esTypes: ['long'], + name: 'bytes', + readFromDocValues: true, scripted: false, searchable: true, - aggregatable: true, - readFromDocValues: true, + type: 'number', }); + const dataView = stubDataView; dataView.toSpec = () => ({}); @@ -73,216 +73,210 @@ async function getComponent({ }); const props: UnifiedFieldListItemProps = { - services: getServicesMock(), - stateService, - searchMode: 'documents', dataView: stubDataView, field: finalField, - ...(canFilter && { onAddFilter: jest.fn() }), - ...(isBreakdownSupported && { onAddBreakdownField: jest.fn() }), - onAddFieldToWorkspace: jest.fn(), - onRemoveFieldFromWorkspace: jest.fn(), - onEditField: jest.fn(), - isSelected: selected, - isEmpty: false, groupIndex: 1, + isEmpty: false, + isSelected: selected, itemIndex: 0, + onAddFieldToWorkspace: jest.fn(), + onEditField: jest.fn(), + onRemoveFieldFromWorkspace: jest.fn(), + searchMode: 'documents', + services: getServicesMock(), size: 'xs', + stateService, workspaceSelectedFieldNames: [], + ...(canFilter && { onAddFilter: jest.fn() }), + ...(isBreakdownSupported && { onAddBreakdownField: jest.fn() }), }; - const comp = await mountWithIntl( + + const user = userEvent.setup(); + + renderWithKibanaRenderContext( ); - // wait for lazy modules - await new Promise((resolve) => setTimeout(resolve, 0)); - await comp.update(); - return { comp, props }; -} - -describe('UnifiedFieldListItem', function () { - it('should allow selecting fields', async function () { - const { comp, props } = await getComponent({}); - findTestSubject(comp, 'fieldToggle-bytes').simulate('click'); + + return { field: finalField, props, user }; +}; + +describe('UnifiedFieldListItem', () => { + it('should allow selecting fields', async () => { + const { props, user } = await renderComponent({}); + + await user.click(screen.getByRole('button', { name: 'Add "bytes" field' })); + expect(props.onAddFieldToWorkspace).toHaveBeenCalledWith(props.field); }); - it('should allow deselecting fields', async function () { - const { comp, props } = await getComponent({ selected: true }); - findTestSubject(comp, 'fieldToggle-bytes').simulate('click'); + + it('should allow deselecting fields', async () => { + const { props, user } = await renderComponent({ selected: true }); + + await user.click(screen.getByRole('button', { name: 'Remove "bytes" field' })); + expect(props.onRemoveFieldFromWorkspace).toHaveBeenCalledWith(props.field); }); - it('displays warning for conflicting fields', async function () { + + it('displays warning for conflicting fields', async () => { const field = new DataViewField({ - name: 'troubled_field', - type: 'conflict', - esTypes: ['integer', 'text'], - searchable: true, aggregatable: true, + esTypes: ['integer', 'text'], + name: 'troubled_field', readFromDocValues: false, + searchable: true, + type: 'conflict', }); - const { comp } = await getComponent({ - selected: true, + + await renderComponent({ field, + selected: true, }); - const fieldInfoIcon = findTestSubject(comp, 'kbnFieldButton_fieldInfoIcon'); - expect(fieldInfoIcon.exists()).toBe(true); + expect(await screen.findByText('Conflict')).toBeVisible(); + expect(screen.getByText('troubled_field')).toBeVisible(); + expect(screen.getByText('Info')).toBeVisible(); }); - it('should not enable the popover if onAddFilter is not provided', async function () { + + it('should not enable the popover if onAddFilter is not provided', async () => { const field = new DataViewField({ - name: '_source', - type: '_source', - esTypes: ['_source'], - searchable: true, aggregatable: true, + esTypes: ['_source'], + name: '_source', readFromDocValues: true, + searchable: true, + type: '_source', }); - const { comp } = await getComponent({ - selected: true, - field, + + await renderComponent({ canFilter: false, + field, + selected: true, }); - expect(comp.find(FieldItemButton).prop('onClick')).toBeUndefined(); + await waitFor(() => { + expect(screen.getAllByText('_source')).toHaveLength(2); + }); + expect( + screen.queryByRole('button', { name: 'Preview _source: _source' }) + ).not.toBeInTheDocument(); + expect(screen.queryByTestId('fieldStats-title')).not.toBeInTheDocument(); }); - it('should not show addBreakdownField action button if not supported', async function () { + it('should not show addBreakdownField action button if not supported', async () => { const field = new DataViewField({ - name: 'extension.keyword', - type: 'string', - esTypes: ['keyword'], aggregatable: true, + esTypes: ['keyword'], + name: 'extension.keyword', searchable: true, + type: 'string', }); - const { comp } = await getComponent({ + + const { user } = await renderComponent({ field, isBreakdownSupported: false, }); - await act(async () => { - const fieldItem = findTestSubject(comp, 'field-extension.keyword-showDetails'); - await fieldItem.simulate('click'); - await comp.update(); - }); + await user.click(screen.getByText('extension.keyword')); - await comp.update(); + await waitFor(() => { + expect(screen.getByRole('heading', { name: 'extension.keyword' })).toBeVisible(); + }); - expect( - comp - .find('[data-test-subj="fieldPopoverHeader_addBreakdownField-extension.keyword"]') - .exists() - ).toBeFalsy(); + expect(screen.queryByRole('button', { name: 'Add breakdown' })).not.toBeInTheDocument(); }); - it('should request field stats', async function () { + + it('should request field stats', async () => { const field = new DataViewField({ - name: 'machine.os.raw', - type: 'string', - esTypes: ['keyword'], aggregatable: true, + esTypes: ['keyword'], + name: 'machine.os.raw', searchable: true, + type: 'string', }); - const { comp } = await getComponent({ field, canFilter: true }); - - await act(async () => { - const fieldItem = findTestSubject(comp, 'field-machine.os.raw-showDetails'); - await fieldItem.simulate('click'); - await comp.update(); - }); - - await comp.update(); + const { user } = await renderComponent({ canFilter: true, field }); - expect(comp.find(EuiPopover).prop('isOpen')).toBe(true); + await user.click(screen.getByText('machine.os.raw')); - await new Promise((resolve) => setTimeout(resolve, 0)); - await comp.update(); + await waitFor(() => { + expect(screen.getByText('Top values')).toBeVisible(); + }); - expect(findTestSubject(comp, 'fieldStats-title').text()).toBe('Top values'); - expect(findTestSubject(comp, 'fieldStats-topValues-bucket')).toHaveLength(2); - expect(findTestSubject(comp, 'fieldStats-topValues-formattedFieldValue').first().text()).toBe( - 'osx' + expect(screen.getByRole('progressbar', { name: 'osx' })).toHaveAttribute( + 'aria-valuetext', + '62.9%' + ); + expect(screen.getByRole('progressbar', { name: 'winx' })).toHaveAttribute( + 'aria-valuetext', + '37.1%' + ); + expect(screen.getAllByRole('progressbar')).toHaveLength(2); + expect(screen.getAllByRole('button', { name: /machine\.os\.raw: "(osx|winx)"/ })).toHaveLength( + 4 ); - expect(comp.find(EuiProgress)).toHaveLength(2); - expect(findTestSubject(comp, 'fieldStats-topValues').find(EuiButtonIcon)).toHaveLength(4); }); - it('should include popover actions', async function () { + + it('should include popover actions', async () => { const field = new DataViewField({ - name: 'extension.keyword', - type: 'string', - esTypes: ['keyword'], aggregatable: true, + esTypes: ['keyword'], + name: 'extension.keyword', searchable: true, + type: 'string', }); - const { comp, props } = await getComponent({ field, canFilter: true }); + const { props, user } = await renderComponent({ field, canFilter: true }); - await act(async () => { - const fieldItem = findTestSubject(comp, 'field-extension.keyword-showDetails'); - await fieldItem.simulate('click'); - await comp.update(); + await user.click(screen.getByText('extension.keyword')); + + await waitFor(() => { + expect(screen.getByRole('heading', { name: 'extension.keyword' })).toBeVisible(); }); - await comp.update(); + const popover = within(screen.getByRole('dialog')); - expect(comp.find(EuiPopover).prop('isOpen')).toBe(true); - expect( - comp - .find('[data-test-subj="fieldPopoverHeader_addBreakdownField-extension.keyword"]') - .exists() - ).toBeTruthy(); + expect(popover.getByRole('button', { name: 'Add breakdown' })).toBeVisible(); + expect(popover.getByRole('button', { name: 'Add "extension.keyword" field' })).toBeVisible(); + expect(popover.getByRole('button', { name: 'Filter for field present' })).toBeVisible(); + expect(popover.getByRole('button', { name: 'Edit data view field' })).toBeVisible(); expect( - comp.find('[data-test-subj="fieldPopoverHeader_addField-extension.keyword"]').exists() - ).toBeTruthy(); - expect( - comp.find('[data-test-subj="fieldPopoverHeader_addExistsFilter-extension.keyword"]').exists() - ).toBeTruthy(); - expect( - comp.find('[data-test-subj="fieldPopoverHeader_editField-extension.keyword"]').exists() - ).toBeTruthy(); - expect( - comp.find('[data-test-subj="fieldPopoverHeader_deleteField-extension.keyword"]').exists() - ).toBeFalsy(); + popover.queryByRole('button', { name: 'Delete data view field' }) + ).not.toBeInTheDocument(); - await act(async () => { - const fieldItem = findTestSubject(comp, 'fieldPopoverHeader_addField-extension.keyword'); - await fieldItem.simulate('click'); - await comp.update(); - }); + await user.click(popover.getByRole('button', { name: 'Add "extension.keyword" field' })); expect(props.onAddFieldToWorkspace).toHaveBeenCalledWith(field); - await comp.update(); - - expect(comp.find(EuiPopover).prop('isOpen')).toBe(false); + await waitFor(() => { + expect(screen.queryByTestId('fieldStats-title')).not.toBeInTheDocument(); + }); }); - it('should not include + action for selected fields', async function () { + it('should not include + action for selected fields', async () => { const field = new DataViewField({ - name: 'extension.keyword', - type: 'string', - esTypes: ['keyword'], aggregatable: true, + esTypes: ['keyword'], + name: 'extension.keyword', searchable: true, + type: 'string', }); - const { comp } = await getComponent({ - field, + const { user } = await renderComponent({ canFilter: true, + field, selected: true, }); - await act(async () => { - const fieldItem = findTestSubject(comp, 'field-extension.keyword-showDetails'); - await fieldItem.simulate('click'); - await comp.update(); - }); + await user.click(screen.getByText('extension.keyword')); - await comp.update(); + await waitFor(() => { + expect(screen.getByRole('heading', { name: 'extension.keyword' })).toBeVisible(); + }); - expect(comp.find(EuiPopover).prop('isOpen')).toBe(true); expect( - comp.find('[data-test-subj="fieldPopoverHeader_addField-extension.keyword"]').exists() - ).toBeFalsy(); + screen.queryByRole('button', { name: 'Add "extension.keyword" field' }) + ).not.toBeInTheDocument(); }); }); diff --git a/src/platform/packages/shared/kbn-unified-histogram/components/chart/histogram.test.tsx b/src/platform/packages/shared/kbn-unified-histogram/components/chart/histogram.test.tsx index 65db50a52e70d..ed7391211cfa8 100644 --- a/src/platform/packages/shared/kbn-unified-histogram/components/chart/histogram.test.tsx +++ b/src/platform/packages/shared/kbn-unified-histogram/components/chart/histogram.test.tsx @@ -7,49 +7,63 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { mountWithIntl } from '@kbn/test-jest-helpers'; import type { HistogramProps } from './histogram'; -import { Histogram } from './histogram'; +import type { UnifiedHistogramFetch$ } from '../../types'; import React from 'react'; +import { act, screen } from '@testing-library/react'; +import { allSuggestionsMock } from '../../__mocks__/suggestions'; import { BehaviorSubject } from 'rxjs'; -import { unifiedHistogramServicesMock } from '../../__mocks__/services'; -import { getLensVisMock } from '../../__mocks__/lens_vis'; -import { dataViewWithTimefieldMock } from '../../__mocks__/data_view_with_timefield'; import { createDefaultInspectorAdapters } from '@kbn/expressions-plugin/common'; -import type { UnifiedHistogramFetch$ } from '../../types'; -import { act } from 'react-dom/test-utils'; -import { RequestStatus } from '@kbn/inspector-plugin/public'; -import { getLensProps, useLensProps } from './hooks/use_lens_props'; +import { dataViewWithTimefieldMock } from '../../__mocks__/data_view_with_timefield'; import { getFetch$Mock, getFetchParamsMock } from '../../__mocks__/fetch_params'; +import { getLensProps, useLensProps } from './hooks/use_lens_props'; +import { getLensVisMock } from '../../__mocks__/lens_vis'; +import { Histogram } from './histogram'; +import { renderWithI18n } from '@kbn/test-jest-helpers'; +import { RequestStatus } from '@kbn/inspector-plugin/public'; +import { unifiedHistogramServicesMock } from '../../__mocks__/services'; + +type CombinedProps = Omit & + Parameters[0]; const getMockLensAttributes = async () => { const query = { language: 'kuery', query: '', }; + return ( await getLensVisMock({ - filters: [], - query, + breakdownField: dataViewWithTimefieldMock.getFieldByName('extension'), columns: [], - isPlainRecord: false, dataView: dataViewWithTimefieldMock, + filters: [], + isPlainRecord: false, + query, timeInterval: 'auto', - breakdownField: dataViewWithTimefieldMock.getFieldByName('extension'), }) ).visContext; }; -type CombinedProps = Omit & - Parameters[0]; +const getEmbeddableProps = () => { + const embeddable = unifiedHistogramServicesMock.lens.EmbeddableComponent as jest.Mock; + expect(embeddable).toHaveBeenCalled(); + + return embeddable.mock.calls[embeddable.mock.calls.length - 1][0]; +}; -async function mountComponent(isPlainRecord = false, hasLensSuggestions = false) { +const renderComponent = async ({ + isPlainRecord = false, + hasLensSuggestions = false, +}: { isPlainRecord?: boolean; hasLensSuggestions?: boolean } = {}) => { const services = unifiedHistogramServicesMock; + services.data.query.timefilter.timefilter.getAbsoluteTime = () => { return { from: '2020-05-14T11:05:13.590', to: '2020-05-14T11:20:13.590' }; }; const fetch$: UnifiedHistogramFetch$ = getFetch$Mock(); + const fetchParams = getFetchParamsMock({ searchSessionId: '123', dataView: dataViewWithTimefieldMock, @@ -61,15 +75,18 @@ async function mountComponent(isPlainRecord = false, hasLensSuggestions = false) from: '2020-05-14T11:05:13.590', to: '2020-05-14T11:20:13.590', }, + query: isPlainRecord ? { esql: 'FROM index1' } : undefined, }); + const lensVisMock = await getLensVisMock({ + allSuggestions: hasLensSuggestions ? allSuggestionsMock : undefined, + breakdownField: dataViewWithTimefieldMock.getFieldByName('extension'), + columns: [], dataView: fetchParams.dataView, - isPlainRecord: fetchParams.isESQLQuery, - timeInterval: fetchParams.timeInterval, filters: fetchParams.filters, + isPlainRecord: fetchParams.isESQLQuery, query: fetchParams.query, - columns: [], - breakdownField: dataViewWithTimefieldMock.getFieldByName('extension'), + timeInterval: fetchParams.timeInterval, }); const props: CombinedProps = { @@ -87,60 +104,77 @@ async function mountComponent(isPlainRecord = false, hasLensSuggestions = false) bucketInterval: undefined, visContext: lensVisMock.visContext!, }; + const Wrapper = (wrapperProps: CombinedProps) => { const lensPropsContext = useLensProps(wrapperProps); + return lensPropsContext ? : null; }; - const component = mountWithIntl(); + renderWithI18n(); act(() => { fetch$?.next({ fetchParams, lensVisServiceState: lensVisMock.lensService.state$.getValue() }); }); - return { props, fetch$, fetchParams, component: component.update(), lensVisMock }; -} + return { fetch$, fetchParams, lensVisMock, props }; +}; describe('Histogram', () => { + beforeEach(() => { + (unifiedHistogramServicesMock.lens.EmbeddableComponent as jest.Mock) + .mockClear() + .mockImplementation(() =>
Lens embeddable
); + }); + it('renders correctly', async () => { - const { component } = await mountComponent(); - expect(component.find('[data-test-subj="unifiedHistogramChart"]').exists()).toBe(true); + await renderComponent(); + + expect(screen.getByText('Lens embeddable')).toBeVisible(); }); it('should only update lens.EmbeddableComponent props when fetch$ is triggered', async () => { - const { component, fetch$, fetchParams, lensVisMock } = await mountComponent(); - const embeddable = unifiedHistogramServicesMock.lens.EmbeddableComponent; - expect(component.find(embeddable).exists()).toBe(true); - let lensProps = component.find(embeddable).props(); + const { fetch$, fetchParams, lensVisMock } = await renderComponent(); + + expect(unifiedHistogramServicesMock.lens.EmbeddableComponent).toHaveBeenCalled(); + + let lensProps = getEmbeddableProps(); + const originalProps = getLensProps({ - searchSessionId: fetchParams.searchSessionId, - timeRange: fetchParams.timeRange, - esqlVariables: fetchParams.esqlVariables, attributes: (await getMockLensAttributes())!.attributes, - onLoad: lensProps.onLoad!, + esqlVariables: fetchParams.esqlVariables, lastReloadRequestTime: fetchParams.lastReloadRequestTime, + onLoad: lensProps.onLoad!, + searchSessionId: fetchParams.searchSessionId, + timeRange: fetchParams.timeRange, }); + expect(lensProps).toMatchObject(expect.objectContaining(originalProps)); + const updatedFetchParams = { ...fetchParams, searchSessionId: '321' }; - await act(async () => { + + act(() => { fetch$.next({ fetchParams: updatedFetchParams, lensVisServiceState: lensVisMock.lensService.state$.getValue(), }); }); - component.update(); - lensProps = component.find(embeddable).props(); + + lensProps = getEmbeddableProps(); + expect(lensProps).toMatchObject( expect.objectContaining({ ...originalProps, searchSessionId: '321' }) ); }); it('should execute onLoad correctly', async () => { - const { component, props } = await mountComponent(); - const embeddable = unifiedHistogramServicesMock.lens.EmbeddableComponent; - const onLoad = component.find(embeddable).props().onLoad!; + const { props } = await renderComponent(); + + const onLoad = getEmbeddableProps().onLoad!; const adapters = createDefaultInspectorAdapters(); + adapters.tables.tables.unifiedHistogram = { meta: { statistics: { totalCount: 100 } } } as any; + const rawResponse = { took: 0, timed_out: false, @@ -187,36 +221,47 @@ describe('Histogram', () => { }, }, }; + jest .spyOn(adapters.requests, 'getRequests') .mockReturnValue([{ response: { json: { rawResponse } } } as any]); + const dataLoading$ = new BehaviorSubject(false); + onLoad(true, undefined, dataLoading$); + expect(props.onLoad).toHaveBeenLastCalledWith(true, undefined, dataLoading$); + act(() => { onLoad?.(false, adapters, dataLoading$); }); + expect(props.onLoad).toHaveBeenLastCalledWith(false, adapters, dataLoading$); }); it('should execute onLoad correctly when the request has a failure status', async () => { - const { component, props } = await mountComponent(); - const embeddable = unifiedHistogramServicesMock.lens.EmbeddableComponent; - const onLoad = component.find(embeddable).props().onLoad!; + const { props } = await renderComponent(); + + const onLoad = getEmbeddableProps().onLoad!; const adapters = createDefaultInspectorAdapters(); + jest .spyOn(adapters.requests, 'getRequests') .mockReturnValue([{ status: RequestStatus.ERROR } as any]); + onLoad?.(false, adapters); + expect(props.onLoad).toHaveBeenLastCalledWith(false, adapters); }); it('should execute onLoad correctly when the response has shard failures', async () => { - const { component, props } = await mountComponent(); - const embeddable = unifiedHistogramServicesMock.lens.EmbeddableComponent; - const onLoad = component.find(embeddable).props().onLoad!; + const { props } = await renderComponent(); + + const onLoad = getEmbeddableProps().onLoad!; const adapters = createDefaultInspectorAdapters(); + adapters.tables.tables.unifiedHistogram = { meta: { statistics: { totalCount: 100 } } } as any; + const rawResponse = { _shards: { total: 1, @@ -231,20 +276,24 @@ describe('Histogram', () => { hits: [], }, }; + jest .spyOn(adapters.requests, 'getRequests') .mockReturnValue([{ response: { json: { rawResponse } } } as any]); + act(() => { onLoad?.(false, adapters); }); + expect(props.onLoad).toHaveBeenLastCalledWith(false, adapters); }); it('should execute onLoad correctly for textbased language and no Lens suggestions', async () => { - const { component, props } = await mountComponent(true, false); - const embeddable = unifiedHistogramServicesMock.lens.EmbeddableComponent; - const onLoad = component.find(embeddable).props().onLoad!; + const { props } = await renderComponent({ isPlainRecord: true, hasLensSuggestions: false }); + + const onLoad = getEmbeddableProps().onLoad!; const adapters = createDefaultInspectorAdapters(); + adapters.tables.tables.layerId = { meta: { type: 'es_ql' }, columns: [ @@ -266,17 +315,20 @@ describe('Histogram', () => { }, ], } as any; + act(() => { onLoad?.(false, adapters); }); + expect(props.onLoad).toHaveBeenLastCalledWith(false, adapters); }); it('should execute onLoad correctly for textbased language and Lens suggestions', async () => { - const { component, props } = await mountComponent(true, true); - const embeddable = unifiedHistogramServicesMock.lens.EmbeddableComponent; - const onLoad = component.find(embeddable).props().onLoad!; + const { props } = await renderComponent({ isPlainRecord: true, hasLensSuggestions: true }); + + const onLoad = getEmbeddableProps().onLoad!; const adapters = createDefaultInspectorAdapters(); + adapters.tables.tables.layerId = { meta: { type: 'es_ql' }, columns: [ @@ -298,9 +350,11 @@ describe('Histogram', () => { }, ], } as any; + act(() => { onLoad?.(false, adapters); }); + expect(props.onLoad).toHaveBeenLastCalledWith(false, adapters); }); }); diff --git a/src/platform/plugins/shared/data/public/utils/table_inspector_view/components/__snapshots__/data_view.test.tsx.snap b/src/platform/plugins/shared/data/public/utils/table_inspector_view/components/__snapshots__/data_view.test.tsx.snap deleted file mode 100644 index d4f6186ad9c50..0000000000000 --- a/src/platform/plugins/shared/data/public/utils/table_inspector_view/components/__snapshots__/data_view.test.tsx.snap +++ /dev/null @@ -1,700 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Inspector Data View component should render empty state 1`] = ` -
-
-
-

- No data available -

-
-
-

- The element did not provide any data. -

-
-
-
-
-`; - -exports[`Inspector Data View component should render loading state 1`] = ` - - loading -
- } - intl={ - Object { - "$t": [Function], - "defaultFormats": Object { - "date": Object { - "full": Object { - "day": "numeric", - "month": "long", - "weekday": "long", - "year": "numeric", - }, - "long": Object { - "day": "numeric", - "month": "long", - "year": "numeric", - }, - "medium": Object { - "day": "numeric", - "month": "short", - "year": "numeric", - }, - "short": Object { - "day": "numeric", - "month": "numeric", - "year": "2-digit", - }, - }, - "number": Object { - "currency": Object { - "style": "currency", - }, - "percent": Object { - "style": "percent", - }, - }, - "relative": Object { - "days": Object { - "style": "long", - }, - "hours": Object { - "style": "long", - }, - "minutes": Object { - "style": "long", - }, - "months": Object { - "style": "long", - }, - "seconds": Object { - "style": "long", - }, - "years": Object { - "style": "long", - }, - }, - "time": Object { - "full": Object { - "hour": "numeric", - "minute": "numeric", - "second": "numeric", - "timeZoneName": "short", - }, - "long": Object { - "hour": "numeric", - "minute": "numeric", - "second": "numeric", - "timeZoneName": "short", - }, - "medium": Object { - "hour": "numeric", - "minute": "numeric", - "second": "numeric", - }, - "short": Object { - "hour": "numeric", - "minute": "numeric", - }, - }, - }, - "defaultLocale": "en", - "fallbackOnEmptyString": true, - "formatDate": [Function], - "formatDateTimeRange": [Function], - "formatDateToParts": [Function], - "formatDisplayName": [Function], - "formatList": [Function], - "formatListToParts": [Function], - "formatMessage": [Function], - "formatNumber": [Function], - "formatNumberToParts": [Function], - "formatPlural": [Function], - "formatRelativeTime": [Function], - "formatTime": [Function], - "formatTimeToParts": [Function], - "formats": Object {}, - "formatters": Object { - "getDateTimeFormat": [Function], - "getDisplayNames": [Function], - "getListFormat": [Function], - "getMessageFormat": [Function], - "getNumberFormat": [Function], - "getPluralRules": [Function], - "getRelativeTimeFormat": [Function], - }, - "locale": "en", - "messages": Object {}, - "onError": [Function], - "onWarn": [Function], - "timeZone": undefined, - } - } -> - -
- loading -
- -`; - -exports[`Inspector Data View component should render single table without selector 1`] = ` -Array [ -
-
-
-
- -
-
-
, -
, -
-
- - - - - - - - - - - -
-
- - - -
-
-
-
- 123 -
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
- -
-
-
-
, -] -`; - -exports[`Inspector Data View component should support multiple datatables 1`] = ` -Array [ -
-
-
-

- There are 2 tables in total -

-
-
-
-
- - Selected: - -
-
-
-
- -
-
-
-
-
-
-
- -
-
-
, -
, -
-
- - - - - - - - - - - -
-
- - - -
-
-
-
- 123 -
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
- -
-
-
-
, -] -`; diff --git a/src/platform/plugins/shared/data/public/utils/table_inspector_view/components/data_view.test.tsx b/src/platform/plugins/shared/data/public/utils/table_inspector_view/components/data_view.test.tsx index 02649f39ac071..a20267c31c7a5 100644 --- a/src/platform/plugins/shared/data/public/utils/table_inspector_view/components/data_view.test.tsx +++ b/src/platform/plugins/shared/data/public/utils/table_inspector_view/components/data_view.test.tsx @@ -7,105 +7,117 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ +import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public'; +import type { InspectorViewDescription } from '@kbn/inspector-plugin/public'; +import type { IUiSettingsClient } from '@kbn/core/public'; +import type { UiActionsStart } from '@kbn/ui-actions-plugin/public'; import React, { Suspense } from 'react'; +import userEvent from '@testing-library/user-event'; +import { act, screen } from '@testing-library/react'; import { getTableViewDescription } from '..'; -import { mountWithIntl } from '@kbn/test-jest-helpers'; -import { TablesAdapter } from '@kbn/expressions-plugin/common'; +import { renderWithI18n } from '@kbn/test-jest-helpers'; +import { TablesAdapter, type Datatable } from '@kbn/expressions-plugin/common'; jest.mock('@kbn/share-plugin/public', () => ({ downloadMultipleAs: jest.fn(), })); + jest.mock('../../../../common', () => ({ datatableToCSV: jest.fn().mockReturnValue('csv'), tableHasFormulas: jest.fn().mockReturnValue(false), })); describe('Inspector Data View', () => { - let DataView: any; + let DataView: InspectorViewDescription; + + const createDatatable = (value: number): Datatable => ({ + columns: [{ id: '1', name: 'column1', meta: { type: 'number' } }], + rows: [{ '1': value }], + type: 'datatable', + }); beforeEach(() => { DataView = getTableViewDescription(() => ({ - uiActions: {} as any, - uiSettings: { get: (key: string, value: string) => value } as any, fieldFormats: { deserialize: jest.fn().mockReturnValue({ convertToText: (v: string) => v }), - } as any, + } as unknown as FieldFormatsStart, isFilterable: jest.fn(), + uiActions: {} as UiActionsStart, + uiSettings: { get: (_key: string, value: string) => value } as IUiSettingsClient, })); }); it('should only show if data adapter is present', () => { const adapter = new TablesAdapter(); - expect(DataView.shouldShow({ tables: adapter })).toBe(true); - expect(DataView.shouldShow({})).toBe(false); + expect(DataView.shouldShow?.({ tables: adapter })).toBe(true); + expect(DataView.shouldShow?.({})).toBe(false); }); describe('component', () => { - let adapters: any; + let adapters: { tables: TablesAdapter }; + let InspectorDataView: NonNullable; + + const renderInspectorDataView = ( + ui: React.ReactElement, + options?: { fallback?: React.ReactNode } + ) => renderWithI18n({ui}); beforeEach(() => { adapters = { tables: new TablesAdapter() }; + InspectorDataView = DataView.component!; }); - it('should render loading state', () => { - const DataViewComponent = DataView.component; - const component = mountWithIntl( - loading
}> - - - ); + it('should render loading state', async () => { + renderInspectorDataView(, { + fallback:
loading
, + }); - expect(component).toMatchSnapshot(); + expect(screen.getByText('loading')).toBeVisible(); + expect(await screen.findByText('No data available')).toBeVisible(); }); it('should render empty state', async () => { - const component = mountWithIntl(); // eslint-disable-line react/jsx-pascal-case - adapters.tables.logDatatable({ columns: [{ id: '1' }], rows: [{ '1': 123 }] }); - // After the loader has resolved we'll still need one update, to "flush" the state changes - component.update(); - expect(component.render()).toMatchSnapshot(); + renderInspectorDataView(); + + expect(await screen.findByText('No data available')).toBeVisible(); + expect(screen.getByText('The element did not provide any data.')).toBeVisible(); }); it('should render single table without selector', async () => { - const component = mountWithIntl( - // eslint-disable-next-line react/jsx-pascal-case - - ); - adapters.tables.logDatatable('table1', { - columns: [{ id: '1', name: 'column1', meta: { type: 'number' } }], - rows: [{ '1': 123 }], - type: 'datatable', + renderInspectorDataView(); + + act(() => { + adapters.tables.logDatatable('table1', createDatatable(123)); }); - // After the loader has resolved we'll still need one update, to "flush" the state changes - component.update(); - expect(component.find('[data-test-subj="inspectorDataViewSelectorLabel"]')).toHaveLength(0); - expect(component.render()).toMatchSnapshot(); + expect(await screen.findByText('column1')).toBeVisible(); + + expect(screen.queryByText(/There are \d+ tables? in total/)).not.toBeInTheDocument(); + expect(screen.getByText('123')).toBeVisible(); + expect(screen.getByRole('button', { name: /Download CSV/i })).toBeVisible(); }); it('should support multiple datatables', async () => { const multitableAdapter = { tables: new TablesAdapter() }; + const user = userEvent.setup(); - const component = mountWithIntl( - // eslint-disable-next-line react/jsx-pascal-case - - ); - multitableAdapter.tables.logDatatable('table1', { - columns: [{ id: '1', name: 'column1', meta: { type: 'number' } }], - rows: [{ '1': 123 }], - type: 'datatable', - }); - multitableAdapter.tables.logDatatable('table2', { - columns: [{ id: '1', name: 'column1', meta: { type: 'number' } }], - rows: [{ '1': 456 }], - type: 'datatable', + renderInspectorDataView(); + + act(() => { + multitableAdapter.tables.logDatatable('table1', createDatatable(123)); + multitableAdapter.tables.logDatatable('table2', createDatatable(456)); }); - // After the loader has resolved we'll still need one update, to "flush" the state changes - component.update(); - expect(component.find('[data-test-subj="inspectorDataViewSelectorLabel"]')).toHaveLength(1); - expect(component.render()).toMatchSnapshot(); + expect(await screen.findByText('There are 2 tables in total')).toBeVisible(); + expect(screen.getByText('Table 1')).toBeVisible(); + expect(screen.getByText('column1')).toBeVisible(); + expect(screen.getByText('123')).toBeVisible(); + + await user.click(screen.getByText('Table 1')); + await user.click(await screen.findByText('Table 2')); + + expect(screen.getByText('456')).toBeVisible(); }); }); }); diff --git a/src/platform/plugins/shared/data_view_editor/public/components/form_fields/title_docs_popover.test.tsx b/src/platform/plugins/shared/data_view_editor/public/components/form_fields/title_docs_popover.test.tsx index 3332c5768c438..0fd2ba6d44da7 100644 --- a/src/platform/plugins/shared/data_view_editor/public/components/form_fields/title_docs_popover.test.tsx +++ b/src/platform/plugins/shared/data_view_editor/public/components/form_fields/title_docs_popover.test.tsx @@ -8,21 +8,29 @@ */ import React from 'react'; -import { findTestSubject } from '@elastic/eui/lib/test'; -import { mountWithIntl } from '@kbn/test-jest-helpers'; +import userEvent from '@testing-library/user-event'; +import { renderWithI18n } from '@kbn/test-jest-helpers'; +import { screen } from '@testing-library/react'; import { TitleDocsPopover } from './title_docs_popover'; +import { waitForEuiPopoverOpen } from '@elastic/eui/lib/test/rtl'; describe('DataViewEditor TitleDocsPopover', () => { it('should render normally', async () => { - const component = mountWithIntl(); + const user = userEvent.setup(); - expect(findTestSubject(component, 'indexPatternDocsButton').exists()).toBeTruthy(); - expect(findTestSubject(component, 'indexPatternDocsPopoverContent').exists()).toBeFalsy(); + renderWithI18n(); - findTestSubject(component, 'indexPatternDocsButton').simulate('click'); + expect(screen.getByTestId('indexPatternDocsButton')).toBeVisible(); + expect(screen.queryByTestId('indexPatternDocsPopoverContent')).not.toBeInTheDocument(); - await component.update(); + await user.click(screen.getByTestId('indexPatternDocsButton')); + await waitForEuiPopoverOpen(); - expect(findTestSubject(component, 'indexPatternDocsPopoverContent').exists()).toBeTruthy(); + expect(screen.getByTestId('indexPatternDocsPopoverContent')).toBeVisible(); + expect( + screen.getByText( + 'An index pattern is a string that you use to match one or more data streams, indices, or aliases.' + ) + ).toBeVisible(); }); }); diff --git a/src/platform/plugins/shared/data_view_editor/public/components/preview_panel/preview_panel.test.tsx b/src/platform/plugins/shared/data_view_editor/public/components/preview_panel/preview_panel.test.tsx index bcb8702a8a88e..65048aa189e0d 100644 --- a/src/platform/plugins/shared/data_view_editor/public/components/preview_panel/preview_panel.test.tsx +++ b/src/platform/plugins/shared/data_view_editor/public/components/preview_panel/preview_panel.test.tsx @@ -7,26 +7,32 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import React from 'react'; -import { findTestSubject } from '@elastic/eui/lib/test'; -import { EuiTable, EuiButtonGroup } from '@elastic/eui'; -import { mountWithIntl } from '@kbn/test-jest-helpers'; import type { MatchedItem } from '@kbn/data-views-plugin/public'; -import { INDEX_PATTERN_TYPE } from '@kbn/data-views-plugin/public'; import type { Props as PreviewPanelProps } from './preview_panel'; -import { PreviewPanel } from './preview_panel'; +import React from 'react'; +import userEvent from '@testing-library/user-event'; import { from } from 'rxjs'; +import { INDEX_PATTERN_TYPE } from '@kbn/data-views-plugin/public'; +import { PreviewPanel } from './preview_panel'; +import { renderWithI18n } from '@kbn/test-jest-helpers'; +import { screen, within } from '@testing-library/react'; const indices = [ - { name: 'kibana_1', tags: [] }, - { name: 'kibana_2', tags: [] }, - { name: 'es', tags: [] }, -] as unknown as MatchedItem[]; + { name: 'kibana_1', tags: [], item: { name: 'kibana_1' } }, + { name: 'kibana_2', tags: [], item: { name: 'kibana_2' } }, + { name: 'es', tags: [], item: { name: 'es' } }, +] satisfies MatchedItem[]; + +const expectSelectedViewMode = (label: string) => { + const button = screen.getByText(label).closest('button'); + + expect(button).toHaveClass('euiButtonGroupButton-isSelected'); +}; describe('DataViewEditor PreviewPanel', () => { const commonProps: Omit = { - type: INDEX_PATTERN_TYPE.DEFAULT, allowHidden: false, + type: INDEX_PATTERN_TYPE.DEFAULT, }; it('should render normally by default', async () => { @@ -38,15 +44,26 @@ describe('DataViewEditor PreviewPanel', () => { visibleIndices: indices, }, ]); - const component = await mountWithIntl( - - ); - expect(component.find(EuiTable).exists()).toBeTruthy(); - expect(component.find(EuiButtonGroup).exists()).toBeFalsy(); + renderWithI18n(); + + expect(await screen.findByTestId('createIndexPatternStep1IndicesList')).toBeVisible(); + + expect(screen.getByText('Your index pattern can match 3 sources.')).toBeVisible(); + expect( + within(screen.getByTestId('createIndexPatternStep1IndicesList')).getByText('kibana_1') + ).toBeVisible(); + expect( + within(screen.getByTestId('createIndexPatternStep1IndicesList')).getByText('kibana_2') + ).toBeVisible(); + expect( + within(screen.getByTestId('createIndexPatternStep1IndicesList')).getByText('es') + ).toBeVisible(); + expect(screen.queryByText('Matching sources')).not.toBeInTheDocument(); }); it('should render matching indices and can switch to all indices', async () => { + const user = userEvent.setup(); const matchedIndices$: PreviewPanelProps['matchedIndices$'] = from([ { allIndices: indices, @@ -55,22 +72,26 @@ describe('DataViewEditor PreviewPanel', () => { visibleIndices: [indices[0], indices[1]], }, ]); - const component = await mountWithIntl( - - ); - - expect(component.find(EuiTable).exists()).toBeTruthy(); - expect(component.find(EuiButtonGroup).exists()).toBeTruthy(); - expect(component.find('.euiButtonGroupButton-isSelected').first().text()).toBe( - 'Matching sources' + renderWithI18n( + ); - findTestSubject(component, 'allIndices').simulate('click'); - - await component.update(); - - expect(component.find('.euiButtonGroupButton-isSelected').first().text()).toBe('All sources'); + expect(await screen.findByTestId('createIndexPatternStep1IndicesList')).toBeVisible(); + expect(screen.getByText('Your index pattern matches 2 sources.')).toBeVisible(); + expect(screen.getByText('Visible sources')).toBeVisible(); + expect(screen.getByText('Matching sources')).toBeVisible(); + expectSelectedViewMode('Matching sources'); + expect( + within(screen.getByTestId('createIndexPatternStep1IndicesList')).queryByText('es') + ).not.toBeInTheDocument(); + + await user.click(screen.getByTestId('allIndices')); + + expectSelectedViewMode('All sources'); + expect( + within(screen.getByTestId('createIndexPatternStep1IndicesList')).getByText('es') + ).toBeVisible(); }); it('should render matching indices with warnings', async () => { @@ -82,15 +103,30 @@ describe('DataViewEditor PreviewPanel', () => { visibleIndices: [indices[0], indices[1]], }, ]); - const component = await mountWithIntl( - + + renderWithI18n( + ); - expect(component.find(EuiTable).exists()).toBeTruthy(); - expect(component.find(EuiButtonGroup).exists()).toBeTruthy(); + expect(await screen.findByTestId('createIndexPatternStep1IndicesList')).toBeVisible(); + expect( + screen.getByText("Your index pattern doesn't match any data streams, indices, or index", { + exact: false, + }) + ).toBeVisible(); + expect( + within(screen.getByTestId('createIndexPatternStep1IndicesList')).getAllByText('kib') + ).toHaveLength(2); + within(screen.getByTestId('createIndexPatternStep1IndicesList')) + .getAllByText('kib') + .forEach((highlightedMatch) => { + expect(highlightedMatch.closest('strong')).toBeVisible(); + }); + expect(screen.getByText('Matching sources')).toBeVisible(); }); it('should render all indices tab when ends with a comma and can switch to matching sources', async () => { + const user = userEvent.setup(); const matchedIndices$: PreviewPanelProps['matchedIndices$'] = from([ { allIndices: indices, @@ -99,21 +135,21 @@ describe('DataViewEditor PreviewPanel', () => { visibleIndices: [indices[0]], }, ]); - const component = await mountWithIntl( - - ); - - expect(component.find(EuiTable).exists()).toBeTruthy(); - expect(component.find(EuiButtonGroup).exists()).toBeTruthy(); - expect(component.find('.euiButtonGroupButton-isSelected').first().text()).toBe('All sources'); + renderWithI18n( + + ); - findTestSubject(component, 'onlyMatchingIndices').simulate('click'); + expect(await screen.findByTestId('createIndexPatternStep1IndicesList')).toBeVisible(); + expect(screen.getByText('kibana_1').closest('strong')).toBeVisible(); + expect(screen.getByText('All sources')).toBeVisible(); + expectSelectedViewMode('All sources'); - await component.update(); + await user.click(screen.getByTestId('onlyMatchingIndices')); - expect(component.find('.euiButtonGroupButton-isSelected').first().text()).toBe( - 'Matching sources' - ); + expectSelectedViewMode('Matching sources'); + expect( + within(screen.getByTestId('createIndexPatternStep1IndicesList')).queryByText('es') + ).not.toBeInTheDocument(); }); }); diff --git a/src/platform/plugins/shared/data_view_management/public/components/edit_index_pattern/scripted_fields_table/components/header/__snapshots__/header.test.tsx.snap b/src/platform/plugins/shared/data_view_management/public/components/edit_index_pattern/scripted_fields_table/components/header/__snapshots__/header.test.tsx.snap deleted file mode 100644 index dee84b929278d..0000000000000 --- a/src/platform/plugins/shared/data_view_management/public/components/edit_index_pattern/scripted_fields_table/components/header/__snapshots__/header.test.tsx.snap +++ /dev/null @@ -1,63 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Header should render normally 1`] = ` -
-
-
-

-

-
-
- - Use - - instead of scripted fields. Runtime fields support Painless scripting and provide greater flexibility. You can also use the - - to compute values directly at query time. - -
-
-
-
-

- - Scripted fields can be used in visualizations and displayed in documents. However, they cannot be searched. - -

-
-
-
-`; diff --git a/src/platform/plugins/shared/data_view_management/public/components/edit_index_pattern/scripted_fields_table/components/header/header.test.tsx b/src/platform/plugins/shared/data_view_management/public/components/edit_index_pattern/scripted_fields_table/components/header/header.test.tsx index 8f7d8b2b8ad8b..075aa4c657ea1 100644 --- a/src/platform/plugins/shared/data_view_management/public/components/edit_index_pattern/scripted_fields_table/components/header/header.test.tsx +++ b/src/platform/plugins/shared/data_view_management/public/components/edit_index_pattern/scripted_fields_table/components/header/header.test.tsx @@ -8,22 +8,34 @@ */ import React from 'react'; -import { mountWithI18nProvider } from '@kbn/test-jest-helpers'; +import { Header } from './header'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import { mockManagementPlugin } from '../../../../../mocks'; - -import { Header } from './header'; +import { renderWithKibanaRenderContext } from '@kbn/test-jest-helpers'; +import { screen } from '@testing-library/react'; describe('Header', () => { const mockedContext = mockManagementPlugin.createIndexPatternManagmentContext(); - test('should render normally', () => { - const component = mountWithI18nProvider(
, { - wrappingComponent: KibanaContextProvider, - wrappingComponentProps: { - services: mockedContext, - }, - }); - expect(component.render()).toMatchSnapshot(); + it('should render normally', () => { + renderWithKibanaRenderContext( + +
+ + ); + + expect(screen.getByText('Scripted fields are deprecated')).toBeVisible(); + expect( + screen.getByText('instead of scripted fields. Runtime fields support Painless scripting', { + exact: false, + }) + ).toBeVisible(); + expect(screen.getByText('runtime fields')).toBeVisible(); + expect(screen.getByText('Elasticsearch Query Language (ES|QL)')).toBeVisible(); + expect( + screen.getByText( + 'Scripted fields can be used in visualizations and displayed in documents. However, they cannot be searched.' + ) + ).toBeVisible(); }); });