From d794a70a3305e97b4d8d3dc5c8435b85d50781f1 Mon Sep 17 00:00:00 2001 From: Constance Date: Wed, 2 Feb 2022 13:22:18 -0800 Subject: [PATCH] [EuiDataGrid] Allow consuming applications to control various internal state via `ref` (#5590) * [EuiDataGrid] Set up `ref` that exposes focus/popover internal APIs (#5499) * Set up types * Set up forwardRef * Add setFocusedCell API to returned grid ref obj * Add colIndex prop to cell actions - so that cell actions that trigger modals or flyouts can re-focus into the correct cell using the new ref API * Add documentation + example + props * Add changelog * [PR feedback] Types Co-authored-by: Chandler Prall * [PR feedback] Clean up unit test * [Rebase] Tweak useImperativeHandle location - Moving it below fullscreen logic, as we're oging to expose setIsFullScreen as an API shortly Co-authored-by: Chandler Prall * [EuiDataGrid] Add `setIsFullScreen` to ref API (#5531) * Expose `setIsFullScreen` to ref API * Update documentation/examples * [EuiDataGrid] Add `openCellPopover` and `closeCellPopover` to ref APIs (#5550) * [setup] Update testCustomHook to expose fn that allows accessing most recent state/value - without this callback, the initial returned hook values will be stale/not properly return most recent values - see next commit for example usage within useCellPopover * Set up cell popover context - set up initial open/location state, + open/close popover APIs returned to consumers - improve auto props documentation - remove EuiDataGridCellLocation in favor of specifying rowIndex and colIndex (it's less DRY but it's easier for devs to not have to look up EuiDataGridCellLocation from our docs) * Pass down popoverContext to cells as a prop - I'm not using context here because we're already using this.context for focus, and unfortunately class components can only initialize one context at time using `static contextType` (see https://reactjs.org/docs/context.html#classcontexttype) * Remove internal cell popoverIsOpen state - This should now be handled by the overarching context state, and the cell should simply react to it or update it (similar to how focusContext works) + add new var for hasCellButtons + add unit tests for isFocusedCell alongside isPopoverOpen (since both methods perform similar functions) * Update cell popovers to set the popover anchor & content - content is TODO, will likely be easier to compare when cleaning it up/moving it all at once * Refactor EuiDataGridCellPopover - It should no longer exist as a reusable component that belongs to every single cell, but instead as a single popover that exists at the top grid level and moves from cell to cell - I cleaned and split up the JSX for the popover (e.g. moving popover actions to data_grid_cell_buttons, where it feels like it belongs more) and think it's significantly more DRY now - note the entire `anchorContent` branch removed from EuiDataGridCell that is no longer necessary - Note that due to this change, we now have to mock EuiWrappingPopover in EuiDataGrid tests, as we see failures otherwise * [bugfix] Handle cells with open popover being scrolled out of view - this is the same behavior as in prod - causes weird DOM issues if we don't close the cell popover automatically * [bugfix] Workaround for popover DOM stuttering issues * [enhancement] Account for openCellPopover being called on cells out of view + write bonus Cypress test for useScroll's focus effect now that we have access to the imperative ref * Update documentation example + remove code snippet - it was starting to get redundant with the API bullet points, and is already available as tab if needed + fix control button widths * [PR feedback] Be more specific about useImperativeHandle dependencies + add a few explanatory comments * [PR feedback] Rename openCellLocation to cellLocation - to make it sound less like a verb/method * [PR feedback] Ignore edge case of `openCellPopover` being called on an `isExpandable: false` cell * [EuiDataGrid] Handle exposed ref APIs potentially pointing to invalid, off-page, or out of view cells (#5572) * Enable sorting + targeting row indices outside of the current page - to test handling the exposed APIs when dealing with sorted/paginated data * Switch data grid example to Typescript - to test type issues during consumer usage + @ts-ignore faker complaints * Fix cell expansion buttons on paginated pages not working correctly * Attempt to more clearly document `rowIndex`s that are actually `visibleRowIndex`s * [setup] Move imperative handler setup to its own util file - this will let us set up ref-specific helpers & add more comment context without bloating the main file * Add catch/check for cell locations that do not exist in the current grid * Add getVisibleRowIndex helper - Converts the `rowIndex` from the consumer to a `visibleRowIndex` that our internal state can use - Account for sorted grid by finding the inversed index of the `sortedRowMap` - To make this easier, I converted soredRowMap to an array (since it's already only uses numbers for both keys and values), since arrays have a handy .findIndex method - Handles automatically paginating the grid if the targeted cell is on a different page * Replace grid ref Jest tests with more complete Cypress tests * Update documentation with new behavior * [PR feedback] Rename fns to indicate multiple concerns - having a side effect in a getter feels bad, so change that to a `find` - rename use hook to indicate sorting and pagination concerns * Improve changelog * Data grid ref methods docs review * Fix colIndex to be available in renderCellValue as well as cell actions - primarily for use within trailing/leading cells and custom actions - see https://github.com/elastic/eui/pull/5499/commits/1609e45d93f3b960b29070761bb39b57b133dfdd * Fix main datagrid example to restore cell focus on modal/flyout close * [a11y] Fix missing header cell text in main datagrid example Co-authored-by: Chandler Prall Co-authored-by: Elizabet Oliveira --- CHANGELOG.md | 2 + src-docs/src/routes.js | 2 + src-docs/src/views/datagrid/datagrid.js | 24 +- .../src/views/datagrid/datagrid_example.js | 17 + .../views/datagrid/datagrid_ref_example.js | 117 +++ src-docs/src/views/datagrid/ref.tsx | 233 ++++++ .../data_grid_cell.test.tsx.snap | 48 ++ .../datagrid/body/data_grid_body.tsx | 5 +- .../datagrid/body/data_grid_cell.test.tsx | 202 ++++- .../datagrid/body/data_grid_cell.tsx | 165 ++-- .../body/data_grid_cell_buttons.test.tsx | 65 +- .../datagrid/body/data_grid_cell_buttons.tsx | 49 +- .../body/data_grid_cell_popover.test.tsx | 321 ++++---- .../datagrid/body/data_grid_cell_popover.tsx | 137 ++-- .../body/data_grid_footer_row.test.tsx | 52 ++ .../datagrid/body/data_grid_footer_row.tsx | 5 +- .../header/data_grid_header_cell.test.tsx | 2 +- .../controls/display_selector.test.tsx | 4 +- src/components/datagrid/data_grid.test.tsx | 11 +- src/components/datagrid/data_grid.tsx | 773 +++++++++--------- src/components/datagrid/data_grid_types.ts | 77 +- src/components/datagrid/utils/in_memory.tsx | 3 +- src/components/datagrid/utils/ref.spec.tsx | 198 +++++ src/components/datagrid/utils/ref.test.ts | 105 +++ src/components/datagrid/utils/ref.ts | 154 ++++ .../datagrid/utils/scrolling.spec.tsx | 35 +- .../datagrid/utils/scrolling.test.tsx | 116 ++- src/components/datagrid/utils/scrolling.tsx | 17 + src/components/datagrid/utils/sorting.ts | 30 +- src/test/test_custom_hook.test_helper.tsx | 15 +- 30 files changed, 2249 insertions(+), 735 deletions(-) create mode 100644 src-docs/src/views/datagrid/datagrid_ref_example.js create mode 100644 src-docs/src/views/datagrid/ref.tsx create mode 100644 src/components/datagrid/utils/ref.spec.tsx create mode 100644 src/components/datagrid/utils/ref.test.ts create mode 100644 src/components/datagrid/utils/ref.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 99a907b60bf..0150fcadd80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ ## [`main`](https://github.com/elastic/eui/tree/main) +- Added the ability to control internal `EuiDataGrid` fullscreen, cell focus, and cell popover state via the `ref` prop ([#5590](https://github.com/elastic/eui/pull/5590)) + **Bug fixes** - Fixed `EuiInMemoryTable`'s `onTableChange` callback not returning the correct `sort.field` value on pagination ([#5588](https://github.com/elastic/eui/pull/5588)) diff --git a/src-docs/src/routes.js b/src-docs/src/routes.js index 11d3ff15f0a..23c7d8366e5 100644 --- a/src-docs/src/routes.js +++ b/src-docs/src/routes.js @@ -89,6 +89,7 @@ import { DataGridControlColumnsExample } from './views/datagrid/datagrid_control import { DataGridFooterRowExample } from './views/datagrid/datagrid_footer_row_example'; import { DataGridVirtualizationExample } from './views/datagrid/datagrid_virtualization_example'; import { DataGridRowHeightOptionsExample } from './views/datagrid/datagrid_height_options_example'; +import { DataGridRefExample } from './views/datagrid/datagrid_ref_example'; import { DatePickerExample } from './views/date_picker/date_picker_example'; @@ -489,6 +490,7 @@ const navigation = [ DataGridFooterRowExample, DataGridVirtualizationExample, DataGridRowHeightOptionsExample, + DataGridRefExample, TableExample, TableInMemoryExample, ].map((example) => createExample(example)), diff --git a/src-docs/src/views/datagrid/datagrid.js b/src-docs/src/views/datagrid/datagrid.js index a23052743aa..70e0c508fd0 100644 --- a/src-docs/src/views/datagrid/datagrid.js +++ b/src-docs/src/views/datagrid/datagrid.js @@ -6,6 +6,7 @@ import React, { createContext, useContext, useRef, + createRef, } from 'react'; import { Link } from 'react-router-dom'; import { fake } from 'faker'; @@ -29,11 +30,13 @@ import { EuiModalHeader, EuiModalHeaderTitle, EuiPopover, + EuiScreenReaderOnly, EuiText, EuiTitle, } from '../../../../src/components/'; -const DataContext = createContext(); +const gridRef = createRef(); +const DataContext = createContext(); const raw_data = []; for (let i = 1; i < 100; i++) { @@ -216,13 +219,20 @@ const trailingControlColumns = [ { id: 'actions', width: 40, - headerCellRender: () => null, - rowCellRender: function RowCellRender() { + headerCellRender: () => ( + + Controls + + ), + rowCellRender: function RowCellRender({ rowIndex, colIndex }) { const [isPopoverVisible, setIsPopoverVisible] = useState(false); const closePopover = () => setIsPopoverVisible(false); const [isModalVisible, setIsModalVisible] = useState(false); - const closeModal = () => setIsModalVisible(false); + const closeModal = () => { + setIsModalVisible(false); + gridRef.current.setFocusedCell({ rowIndex, colIndex }); + }; const showModal = () => { closePopover(); setIsModalVisible(true); @@ -263,7 +273,10 @@ const trailingControlColumns = [ } const [isFlyoutVisible, setIsFlyoutVisible] = useState(false); - const closeFlyout = () => setIsFlyoutVisible(false); + const closeFlyout = () => { + setIsFlyoutVisible(false); + gridRef.current.setFocusedCell({ rowIndex, colIndex }); + }; const showFlyout = () => { closePopover(); setIsFlyoutVisible(true); @@ -415,6 +428,7 @@ export default () => { onChangePage: onChangePage, }} onColumnResize={onColumnResize.current} + ref={gridRef} /> ); diff --git a/src-docs/src/views/datagrid/datagrid_example.js b/src-docs/src/views/datagrid/datagrid_example.js index 47a606c471c..3ecd3c20e5b 100644 --- a/src-docs/src/views/datagrid/datagrid_example.js +++ b/src-docs/src/views/datagrid/datagrid_example.js @@ -33,6 +33,7 @@ import { EuiDataGridRowHeightsOptions, EuiDataGridCellValueElementProps, EuiDataGridSchemaDetector, + EuiDataGridRefProps, } from '!!prop-loader!../../../../src/components/datagrid/data_grid_types'; const gridSnippet = ` @@ -164,6 +165,8 @@ const gridSnippet = ` ); }, }} + // Optional. For advanced control of internal data grid popover/focus state, passes back an object of API methods + ref={dataGridRef} /> `; @@ -323,6 +326,19 @@ const gridConcepts = [ ), }, + { + title: 'ref', + description: ( + + Passes back an object of internal EuiDataGridRefProps{' '} + methods for advanced control of data grid popover/focus state. See{' '} + + Data grid ref methods + {' '} + for more details and examples. + + ), + }, ]; export const DataGridExample = { @@ -414,6 +430,7 @@ export const DataGridExample = { EuiDataGridToolBarAdditionalControlsLeftOptions, EuiDataGridPopoverContentProps, EuiDataGridRowHeightsOptions, + EuiDataGridRefProps, }, demo: ( diff --git a/src-docs/src/views/datagrid/datagrid_ref_example.js b/src-docs/src/views/datagrid/datagrid_ref_example.js new file mode 100644 index 00000000000..66795c4e25b --- /dev/null +++ b/src-docs/src/views/datagrid/datagrid_ref_example.js @@ -0,0 +1,117 @@ +import React from 'react'; + +import { GuideSectionTypes } from '../../components'; +import { EuiCode, EuiSpacer, EuiCallOut } from '../../../../src/components'; + +import { EuiDataGridRefProps } from '!!prop-loader!../../../../src/components/datagrid/data_grid_types'; +import DataGridRef from './ref'; +const dataGridRefSource = require('!!raw-loader!./ref'); +const dataGridRefSnippet = `const dataGridRef = useRef(); + + +// Mnaually toggle the data grid's full screen state +dataGridRef.current.setIsFullScreen(true); + +// Mnaually focus a specific cell within the data grid +dataGridRef.current.setFocusedCell({ rowIndex, colIndex }); + +// Manually opens the popover of a specified cell within the data grid +dataGridRef.current.openCellPopover({ rowIndex, colIndex }); + +// Close any open cell popover +dataGridRef.current.closeCellPopover(); +`; + +export const DataGridRefExample = { + title: 'Data grid ref methods', + sections: [ + { + source: [ + { + type: GuideSectionTypes.TSX, + code: dataGridRefSource, + }, + ], + text: ( + <> +

+ For advanced use cases, and particularly for data grids that manage + associated modals/flyouts and need to manually control their grid + cell popovers & focus states, we expose certain internal methods via + the ref prop of EuiDataGrid. + These methods are: +

+
    +
  • +

    + setIsFullScreen(isFullScreen) - controls the + full screen state of the data grid. Accepts a true/false boolean + flag. +

    +
  • +
  • + setFocusedCell({'{ rowIndex, colIndex }'}) - + focuses the specified cell in the grid. + + + Your modal or flyout should restore focus into the grid on close + to prevent keyboard or screen reader users from being stranded. + + +
  • +
  • + openCellPopover({'{ rowIndex, colIndex }'}) - + opens the specified cell's popover contents. + +
  • +
  • +

    + closeCellPopover() - closes any currently + open cell popover. +

    +
  • +
+ + + + + When using setFocusedCell or{' '} + openCellPopover, keep in mind: +
    +
  • + colIndex is affected by the user reordering + or hiding columns. +
  • +
  • + If the passed cell indices are outside the data grid's + total row count or visible column count, an error will be + thrown. +
  • +
  • + If the data grid is paginated or sorted, the grid will handle + automatically finding specified row index's correct + location for you. +
  • +
+
+ + + +

+ The below example shows how to use the internal APIs for a data grid + that opens a modal via cell actions. +

+ + ), + components: { DataGridRef }, + demo: , + snippet: dataGridRefSnippet, + props: { EuiDataGridRefProps }, + }, + ], +}; diff --git a/src-docs/src/views/datagrid/ref.tsx b/src-docs/src/views/datagrid/ref.tsx new file mode 100644 index 00000000000..267892df41e --- /dev/null +++ b/src-docs/src/views/datagrid/ref.tsx @@ -0,0 +1,233 @@ +import React, { useCallback, useMemo, useState, useRef } from 'react'; +// @ts-ignore - faker does not have type declarations +import { fake } from 'faker'; + +import { + EuiFlexGroup, + EuiFlexItem, + EuiSpacer, + EuiFormRow, + EuiFieldNumber, + EuiButton, + EuiDataGrid, + EuiDataGridRefProps, + EuiModal, + EuiModalBody, + EuiModalFooter, + EuiModalHeader, + EuiModalHeaderTitle, + EuiText, +} from '../../../../src/components'; + +const raw_data: Array<{ [key: string]: string }> = []; +for (let i = 1; i < 100; i++) { + raw_data.push({ + name: fake('{{name.lastName}}, {{name.firstName}}'), + email: fake('{{internet.email}}'), + location: fake('{{address.city}}, {{address.country}}'), + account: fake('{{finance.account}}'), + date: fake('{{date.past}}'), + }); +} + +export default () => { + const dataGridRef = useRef(null); + + // Modal + const [isModalVisible, setIsModalVisible] = useState(false); + const [lastFocusedCell, setLastFocusedCell] = useState({ + rowIndex: 0, + colIndex: 0, + }); + + const closeModal = useCallback(() => { + setIsModalVisible(false); + dataGridRef.current!.setFocusedCell(lastFocusedCell); // Set the data grid focus back to the cell that opened the modal + }, [lastFocusedCell]); + + const showModal = useCallback(({ rowIndex, colIndex }) => { + setIsModalVisible(true); + dataGridRef.current!.closeCellPopover(); // Close any open cell popovers + setLastFocusedCell({ rowIndex, colIndex }); // Store the cell that opened this modal + }, []); + + const openModalAction = useCallback( + ({ Component, rowIndex, colIndex }) => { + return ( + showModal({ rowIndex, colIndex })} + iconType="faceHappy" + aria-label="Open modal" + > + Open modal + + ); + }, + [showModal] + ); + + // Columns + const columns = useMemo( + () => [ + { + id: 'name', + displayAsText: 'Name', + cellActions: [openModalAction], + }, + { + id: 'email', + displayAsText: 'Email address', + initialWidth: 130, + cellActions: [openModalAction], + }, + { + id: 'location', + displayAsText: 'Location', + cellActions: [openModalAction], + }, + { + id: 'account', + displayAsText: 'Account', + cellActions: [openModalAction], + }, + { + id: 'date', + displayAsText: 'Date', + cellActions: [openModalAction], + }, + ], + [openModalAction] + ); + + // Column visibility + const [visibleColumns, setVisibleColumns] = useState(() => + columns.map(({ id }) => id) + ); + + // Pagination + const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 25 }); + const onChangePage = useCallback((pageIndex) => { + setPagination((pagination) => ({ ...pagination, pageIndex })); + }, []); + const onChangePageSize = useCallback((pageSize) => { + setPagination((pagination) => ({ ...pagination, pageSize })); + }, []); + + // Sorting + const [sortingColumns, setSortingColumns] = useState([]); + const onSort = useCallback((sortingColumns) => { + setSortingColumns(sortingColumns); + }, []); + + // Manual cell focus + const [rowIndexAction, setRowIndexAction] = useState(0); + const [colIndexAction, setColIndexAction] = useState(0); + + return ( + <> + + + + setRowIndexAction(Number(e.target.value))} + compressed + /> + + + + + setColIndexAction(Number(e.target.value))} + compressed + /> + + + + + dataGridRef.current!.setFocusedCell({ + rowIndex: rowIndexAction, + colIndex: colIndexAction, + }) + } + > + Set cell focus + + + + + dataGridRef.current!.openCellPopover({ + rowIndex: rowIndexAction, + colIndex: colIndexAction, + }) + } + > + Open cell popover + + + + dataGridRef.current!.setIsFullScreen(true)} + > + Set grid to full screen + + + + + + + raw_data[rowIndex][columnId] + } + pagination={{ + ...pagination, + pageSizeOptions: [25, 50], + onChangePage: onChangePage, + onChangeItemsPerPage: onChangePageSize, + }} + height={400} + ref={dataGridRef} + /> + {isModalVisible && ( + + + +

Example modal

+
+
+ + + +

+ When closed, this modal should re-focus into the cell that + toggled it. +

+
+
+ + + + Close + + +
+ )} + + ); +}; diff --git a/src/components/datagrid/body/__snapshots__/data_grid_cell.test.tsx.snap b/src/components/datagrid/body/__snapshots__/data_grid_cell.test.tsx.snap index 03fc9306cda..c8489a5a6ee 100644 --- a/src/components/datagrid/body/__snapshots__/data_grid_cell.test.tsx.snap +++ b/src/components/datagrid/body/__snapshots__/data_grid_cell.test.tsx.snap @@ -7,6 +7,19 @@ exports[`EuiDataGridCell renders 1`] = ` interactiveCellId="someId" isExpandable={true} popoverContent={[Function]} + popoverContext={ + Object { + "cellLocation": Object { + "colIndex": 0, + "rowIndex": 0, + }, + "closeCellPopover": [MockFunction], + "openCellPopover": [MockFunction], + "popoverIsOpen": false, + "setPopoverAnchor": [MockFunction], + "setPopoverContent": [MockFunction], + } + } renderCellValue={[Function]} rowHeightUtils={ Object { @@ -118,6 +131,7 @@ exports[`EuiDataGridCell renders 1`] = ` style={Object {}} > `; + +exports[`EuiDataGridCell componentDidUpdate handles the cell popover by forwarding the cell's DOM node and contents to the parent popover context 1`] = ` +Array [ +
+
+ + +
+
, +
+
+
+
+
+
, +] +`; diff --git a/src/components/datagrid/body/data_grid_body.tsx b/src/components/datagrid/body/data_grid_body.tsx index 5585073c02b..7acd43a0f1b 100644 --- a/src/components/datagrid/body/data_grid_body.tsx +++ b/src/components/datagrid/body/data_grid_body.tsx @@ -28,6 +28,7 @@ import { EuiDataGridCell } from './data_grid_cell'; import { EuiDataGridFooterRow } from './data_grid_footer_row'; import { EuiDataGridHeaderRow } from './header'; import { DefaultColumnFormatter } from './popover_utils'; +import { DataGridCellPopoverContext } from './data_grid_cell_popover'; import { EuiDataGridBodyProps, EuiDataGridRowManager, @@ -70,6 +71,7 @@ export const Cell: FunctionComponent = ({ rowHeightUtils, rowManager, } = data; + const popoverContext = useContext(DataGridCellPopoverContext); const { headerRowHeight } = useContext(DataGridWrapperRowsContext); const { getCorrectRowIndex } = useContext(DataGridSortingContext); @@ -118,7 +120,8 @@ export const Cell: FunctionComponent = ({ rowHeightsOptions, rowHeightUtils, setRowHeight: isFirstColumn ? setRowHeight : undefined, - rowManager: rowManager, + rowManager, + popoverContext, }; if (isLeadingControlColumn) { diff --git a/src/components/datagrid/body/data_grid_cell.test.tsx b/src/components/datagrid/body/data_grid_cell.test.tsx index e74c9efd830..07e6bb5474c 100644 --- a/src/components/datagrid/body/data_grid_cell.test.tsx +++ b/src/components/datagrid/body/data_grid_cell.test.tsx @@ -7,7 +7,7 @@ */ import React from 'react'; -import { mount, ReactWrapper } from 'enzyme'; +import { mount, render, ReactWrapper } from 'enzyme'; import { keys } from '../../../services'; import { mockRowHeightUtils } from '../utils/__mocks__/row_heights'; import { mockFocusContext } from '../utils/__mocks__/focus_context'; @@ -16,6 +16,14 @@ import { DataGridFocusContext } from '../utils/focus'; import { EuiDataGridCell } from './data_grid_cell'; describe('EuiDataGridCell', () => { + const mockPopoverContext = { + popoverIsOpen: false, + cellLocation: { rowIndex: 0, colIndex: 0 }, + closeCellPopover: jest.fn(), + openCellPopover: jest.fn(), + setPopoverAnchor: jest.fn(), + setPopoverContent: jest.fn(), + }; const requiredProps = { rowIndex: 0, visibleRowIndex: 0, @@ -29,7 +37,10 @@ describe('EuiDataGridCell', () => { ), - popoverContent: () =>
popover
, + popoverContent: ({ children }: { children: React.ReactNode }) => ( +
{children}
+ ), + popoverContext: mockPopoverContext, rowHeightUtils: mockRowHeightUtils, }; @@ -51,19 +62,24 @@ describe('EuiDataGridCell', () => { }} /> ); - component.setState({ popoverIsOpen: true }); - - const cellButtons = component.find('EuiDataGridCellButtons'); - expect(component.find('EuiDataGridCellButtons')).toHaveLength(1); + component.setState({ enableInteractions: true }); - // Should handle re-closing the popover correctly + const getCellButtons = () => component.find('EuiDataGridCellButtons'); + expect(getCellButtons()).toHaveLength(1); - (cellButtons.prop('onExpandClick') as Function)(); - expect(component.state('popoverIsOpen')).toEqual(false); + // Should handle opening the popover + (getCellButtons().prop('onExpandClick') as Function)(); + expect(mockPopoverContext.openCellPopover).toHaveBeenCalled(); - component.setState({ popoverIsOpen: true }); - (cellButtons.prop('closePopover') as Function)(); - expect(component.state('popoverIsOpen')).toEqual(false); + // Should handle closing the popover + component.setProps({ + isExpandable: true, + popoverContext: { ...mockPopoverContext, popoverIsOpen: true }, + }); + (getCellButtons().prop('onExpandClick') as Function)(); + expect(mockPopoverContext.closeCellPopover).toHaveBeenCalledTimes(1); + (getCellButtons().prop('closePopover') as Function)(); + expect(mockPopoverContext.closeCellPopover).toHaveBeenCalledTimes(2); }); describe('shouldComponentUpdate', () => { @@ -117,6 +133,19 @@ describe('EuiDataGridCell', () => { it('popoverContent', () => { component.setProps({ popoverContent: () =>
test
}); }); + it('popoverContext.popoverIsOpen', () => { + component.setProps({ + popoverContext: { ...mockPopoverContext, popoverIsOpen: true }, + }); + }); + it('popoverContext.cellLocation', () => { + component.setProps({ + popoverContext: { + ...mockPopoverContext, + cellLocation: { rowIndex: 5, colIndex: 5 }, + }, + }); + }); it('style', () => { component.setProps({ style: {} }); component.setProps({ style: { top: 0 } }); @@ -132,9 +161,6 @@ describe('EuiDataGridCell', () => { it('cellProps', () => { component.setState({ cellProps: {} }); }); - it('popoverIsOpen', () => { - component.setState({ popoverIsOpen: true }); - }); it('isEntered', () => { component.setState({ isEntered: true }); }); @@ -164,6 +190,29 @@ describe('EuiDataGridCell', () => { component.setProps({ columnId: 'newColumnId' }); expect(setState).toHaveBeenCalledWith({ cellProps: {} }); }); + + it("handles the cell popover by forwarding the cell's DOM node and contents to the parent popover context", () => { + const component = mount( +