Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/8.x' into backport/8.x/pr-198000
Browse files Browse the repository at this point in the history
  • Loading branch information
pgayvallet committed Dec 5, 2024
2 parents 5e908a8 + f931939 commit 879a238
Show file tree
Hide file tree
Showing 29 changed files with 1,009 additions and 273 deletions.
8 changes: 7 additions & 1 deletion packages/kbn-esql-editor/src/editor_footer/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,13 @@ export const EditorFooter = memo(function EditorFooter({
/>
)}
<EuiFlexItem grow={false}>
<EuiButtonIcon iconType="documentation" onClick={toggleLanguageComponent} />
<EuiButtonIcon
iconType="documentation"
onClick={toggleLanguageComponent}
aria-label={i18n.translate('esqlEditor.query.documentationAriaLabel', {
defaultMessage: 'Open documentation',
})}
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
Expand Down
167 changes: 167 additions & 0 deletions packages/kbn-grid-layout/grid/grid_layout.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import React from 'react';
import { fireEvent, render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { getSampleLayout } from './test_utils/sample_layout';
import { GridLayout, GridLayoutProps } from './grid_layout';
import { gridSettings, mockRenderPanelContents } from './test_utils/mocks';
import { cloneDeep } from 'lodash';

describe('GridLayout', () => {
const renderGridLayout = (propsOverrides: Partial<GridLayoutProps> = {}) => {
const defaultProps: GridLayoutProps = {
accessMode: 'EDIT',
layout: getSampleLayout(),
gridSettings,
renderPanelContents: mockRenderPanelContents,
onLayoutChange: jest.fn(),
};

const { rerender, ...rtlRest } = render(<GridLayout {...defaultProps} {...propsOverrides} />);

return {
...rtlRest,
rerender: (overrides: Partial<GridLayoutProps>) =>
rerender(<GridLayout {...defaultProps} {...overrides} />),
};
};
const getAllThePanelIds = () =>
screen
.getAllByRole('button', { name: /panelId:panel/i })
.map((el) => el.getAttribute('aria-label')?.replace(/panelId:/g, ''));

const startDragging = (handle: HTMLElement, options = { clientX: 0, clientY: 0 }) => {
fireEvent.mouseDown(handle, options);
};
const moveTo = (options = { clientX: 256, clientY: 128 }) => {
fireEvent.mouseMove(document, options);
};
const drop = (handle: HTMLElement) => {
fireEvent.mouseUp(handle);
};

const assertTabThroughPanel = async (panelId: string) => {
await userEvent.tab(); // tab to drag handle
await userEvent.tab(); // tab to the panel
expect(screen.getByLabelText(`panelId:${panelId}`)).toHaveFocus();
await userEvent.tab(); // tab to the resize handle
};

const expectedInitialOrder = [
'panel1',
'panel5',
'panel2',
'panel3',
'panel7',
'panel6',
'panel8',
'panel4',
'panel9',
'panel10',
];

beforeEach(() => {
jest.clearAllMocks();
});

it(`'renderPanelContents' is not called during dragging`, () => {
renderGridLayout();

expect(mockRenderPanelContents).toHaveBeenCalledTimes(10); // renderPanelContents is called for each of 10 panels
jest.clearAllMocks();

const panel1DragHandle = screen.getAllByRole('button', { name: /drag to move/i })[0];
startDragging(panel1DragHandle);
moveTo({ clientX: 256, clientY: 128 });
expect(mockRenderPanelContents).toHaveBeenCalledTimes(0); // renderPanelContents should not be called during dragging

drop(panel1DragHandle);
expect(mockRenderPanelContents).toHaveBeenCalledTimes(0); // renderPanelContents should not be called after reordering
});

describe('panels order: panels are rendered from left to right, from top to bottom', () => {
it('focus management - tabbing through the panels', async () => {
renderGridLayout();
// we only test a few panels because otherwise that test would execute for too long
await assertTabThroughPanel('panel1');
await assertTabThroughPanel('panel5');
await assertTabThroughPanel('panel2');
await assertTabThroughPanel('panel3');
});
it('on initializing', () => {
renderGridLayout();
expect(getAllThePanelIds()).toEqual(expectedInitialOrder);
});

it('after reordering some panels', async () => {
renderGridLayout();

const panel1DragHandle = screen.getAllByRole('button', { name: /drag to move/i })[0];
startDragging(panel1DragHandle);

moveTo({ clientX: 256, clientY: 128 });
expect(getAllThePanelIds()).toEqual(expectedInitialOrder); // the panels shouldn't be reordered till we drop

drop(panel1DragHandle);
expect(getAllThePanelIds()).toEqual([
'panel2',
'panel5',
'panel3',
'panel7',
'panel1',
'panel8',
'panel6',
'panel4',
'panel9',
'panel10',
]);
});
it('after removing a panel', async () => {
const { rerender } = renderGridLayout();
const sampleLayoutWithoutPanel1 = cloneDeep(getSampleLayout());
delete sampleLayoutWithoutPanel1[0].panels.panel1;
rerender({ layout: sampleLayoutWithoutPanel1 });

expect(getAllThePanelIds()).toEqual([
'panel2',
'panel5',
'panel3',
'panel7',
'panel6',
'panel8',
'panel4',
'panel9',
'panel10',
]);
});
it('after replacing a panel id', async () => {
const { rerender } = renderGridLayout();
const modifiedLayout = cloneDeep(getSampleLayout());
const newPanel = { ...modifiedLayout[0].panels.panel1, id: 'panel11' };
delete modifiedLayout[0].panels.panel1;
modifiedLayout[0].panels.panel11 = newPanel;

rerender({ layout: modifiedLayout });

expect(getAllThePanelIds()).toEqual([
'panel11',
'panel5',
'panel2',
'panel3',
'panel7',
'panel6',
'panel8',
'panel4',
'panel9',
'panel10',
]);
});
});
});
7 changes: 1 addition & 6 deletions packages/kbn-grid-layout/grid/grid_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { useGridLayoutState } from './use_grid_layout_state';
import { isLayoutEqual } from './utils/equality_checks';
import { resolveGridRow } from './utils/resolve_grid_row';

interface GridLayoutProps {
export interface GridLayoutProps {
layout: GridLayoutData;
gridSettings: GridSettings;
renderPanelContents: (panelId: string) => React.ReactNode;
Expand Down Expand Up @@ -121,11 +121,6 @@ export const GridLayout = ({
rowIndex={rowIndex}
renderPanelContents={renderPanelContents}
gridLayoutStateManager={gridLayoutStateManager}
toggleIsCollapsed={() => {
const newLayout = cloneDeep(gridLayoutStateManager.gridLayout$.value);
newLayout[rowIndex].isCollapsed = !newLayout[rowIndex].isCollapsed;
gridLayoutStateManager.gridLayout$.next(newLayout);
}}
setInteractionEvent={(nextInteractionEvent) => {
if (!nextInteractionEvent) {
gridLayoutStateManager.activePanel$.next(undefined);
Expand Down
74 changes: 74 additions & 0 deletions packages/kbn-grid-layout/grid/grid_panel/drag_handle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import React from 'react';

import { EuiIcon, useEuiTheme } from '@elastic/eui';
import { css } from '@emotion/react';
import { euiThemeVars } from '@kbn/ui-theme';
import { i18n } from '@kbn/i18n';
import { PanelInteractionEvent } from '../types';

export const DragHandle = ({
interactionStart,
}: {
interactionStart: (
type: PanelInteractionEvent['type'] | 'drop',
e: React.MouseEvent<HTMLButtonElement, MouseEvent>
) => void;
}) => {
const { euiTheme } = useEuiTheme();
return (
<button
aria-label={i18n.translate('kbnGridLayout.dragHandle.ariaLabel', {
defaultMessage: 'Drag to move',
})}
className="kbnGridPanel__dragHandle"
css={css`
opacity: 0;
display: flex;
cursor: move;
position: absolute;
align-items: center;
justify-content: center;
top: -${euiThemeVars.euiSizeL};
width: ${euiThemeVars.euiSizeL};
height: ${euiThemeVars.euiSizeL};
z-index: ${euiThemeVars.euiZLevel3};
margin-left: ${euiThemeVars.euiSizeS};
border: 1px solid ${euiTheme.border.color};
border-bottom: none;
background-color: ${euiTheme.colors.emptyShade};
border-radius: ${euiThemeVars.euiBorderRadius} ${euiThemeVars.euiBorderRadius} 0 0;
cursor: grab;
transition: ${euiThemeVars.euiAnimSpeedSlow} opacity;
.kbnGridPanel:hover &,
.kbnGridPanel:focus-within &,
&:active,
&:focus {
opacity: 1 !important;
}
&:active {
cursor: grabbing;
}
.kbnGrid--static & {
display: none;
}
`}
onMouseDown={(e) => {
interactionStart('drag', e);
}}
onMouseUp={(e) => {
interactionStart('drop', e);
}}
>
<EuiIcon type="grabOmnidirectional" />
</button>
);
};
68 changes: 68 additions & 0 deletions packages/kbn-grid-layout/grid/grid_panel/grid_panel.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import { GridPanel, GridPanelProps } from './grid_panel';
import { gridLayoutStateManagerMock } from '../test_utils/mocks';

describe('GridPanel', () => {
const mockRenderPanelContents = jest.fn((panelId) => <div>Panel Content {panelId}</div>);
const mockInteractionStart = jest.fn();

const renderGridPanel = (propsOverrides: Partial<GridPanelProps> = {}) => {
return render(
<GridPanel
panelId="panel1"
rowIndex={0}
renderPanelContents={mockRenderPanelContents}
interactionStart={mockInteractionStart}
gridLayoutStateManager={gridLayoutStateManagerMock}
{...propsOverrides}
/>
);
};
afterEach(() => {
jest.clearAllMocks();
});

it('renders panel contents correctly', () => {
renderGridPanel();
expect(screen.getByText('Panel Content panel1')).toBeInTheDocument();
});

describe('drag handle interaction', () => {
it('calls `drag` interactionStart on mouse down', () => {
renderGridPanel();
const dragHandle = screen.getByRole('button', { name: /drag to move/i });
fireEvent.mouseDown(dragHandle);
expect(mockInteractionStart).toHaveBeenCalledWith('drag', expect.any(Object));
});
it('calls `drop` interactionStart on mouse up', () => {
renderGridPanel();
const dragHandle = screen.getByRole('button', { name: /drag to move/i });
fireEvent.mouseUp(dragHandle);
expect(mockInteractionStart).toHaveBeenCalledWith('drop', expect.any(Object));
});
});
describe('resize handle interaction', () => {
it('calls `resize` interactionStart on mouse down', () => {
renderGridPanel();
const resizeHandle = screen.getByRole('button', { name: /resize/i });
fireEvent.mouseDown(resizeHandle);
expect(mockInteractionStart).toHaveBeenCalledWith('resize', expect.any(Object));
});
it('calls `drop` interactionStart on mouse up', () => {
renderGridPanel();
const resizeHandle = screen.getByRole('button', { name: /resize/i });
fireEvent.mouseUp(resizeHandle);
expect(mockInteractionStart).toHaveBeenCalledWith('drop', expect.any(Object));
});
});
});
Loading

0 comments on commit 879a238

Please sign in to comment.