Eui: [EuiDataGrid] How should I test whether a row is rendered?

Created on 11 Feb 2021  路  9Comments  路  Source: elastic/eui

For context: from my understanding, in React you test things mostly as black-box, that is, you don't test each individual function, but rather "when I do x, y happens" and assert the DOM. So, I want to test "when I click this button, a row is added to the table", and "when I click a row's delete button, that row is removed".

Trouble is, when using React Testing Library, the table only outputs

<div class="euiDataGrid euiDataGrid--bordersAll euiDataGrid--headerShade euiDataGrid--footerOverline euiDataGrid--rowHoverHighlight euiDataGrid--stickyFooter" />

without any children!

I imagine I need to wait for some state update at some point, but I've no idea where. Any help would be greatly appreciated.

For convenience, here is a sandbox.


Not sure why but to stop it from bricking I had to add

global.MutationObserver = class {
  constructor(callback) {}
  disconnect() {}
  observe(element, initObject) {}
};

Also worth noting that my actual implementation uses TypeScript but thought I'd use JS to simplify things.

Most helpful comment

I'm having the same issue with react testing library, just wanted to post a new issue when I encountered this one.
The DataGrid component wasn't generating any children when testing. I also tried with a test from your own repo:
the renderCellValue doesn't get called. forked j-m example here

const requiredProps = {
        'aria-label': 'aria-label',
        className: 'testClass1 testClass2',
        'data-test-subj': 'test subject string',
    };

it('renders custom column headers', async () => {
        const component = render(
            <EuiDataGrid
                {...requiredProps}
                columns={[
                    { id: 'A', display: 'Column A' },
                    { id: 'B', display: <div>More Elements</div> },
                ]}
                columnVisibility={{
                    visibleColumns: ['A', 'B'],
                    setVisibleColumns: () => { },
                }}
                rowCount={3}
                renderCellValue={({ rowIndex, columnId }) =>
                    `${rowIndex}, ${columnId}`
                }
            />
        );

        expect(await findByText('Column A')).toBeInTheDocument();

    });

All 9 comments

I'm having the same issue with react testing library, just wanted to post a new issue when I encountered this one.
The DataGrid component wasn't generating any children when testing. I also tried with a test from your own repo:
the renderCellValue doesn't get called. forked j-m example here

const requiredProps = {
        'aria-label': 'aria-label',
        className: 'testClass1 testClass2',
        'data-test-subj': 'test subject string',
    };

it('renders custom column headers', async () => {
        const component = render(
            <EuiDataGrid
                {...requiredProps}
                columns={[
                    { id: 'A', display: 'Column A' },
                    { id: 'B', display: <div>More Elements</div> },
                ]}
                columnVisibility={{
                    visibleColumns: ['A', 'B'],
                    setVisibleColumns: () => { },
                }}
                rowCount={3}
                renderCellValue={({ rowIndex, columnId }) =>
                    `${rowIndex}, ${columnId}`
                }
            />
        );

        expect(await findByText('Column A')).toBeInTheDocument();

    });

@chandlerprall Any ideas what we should do? Apologies for @ ing you, but you always seem to pop up with grids.

If you've tested the DOM output, but used something other than React Testing Library, then I'm all ears; any advice is much appreciated

There is not currently a great solution for this, but I'll try to summarize my thoughts. Couple complications to start with,

  • Because of the virtualization work, only visible cells are rendered (+1 hidden column and row in each scrollable direction)
  • EuiDataGrid responds to its DOM container to determine its size, and consequently what cells are rendered
  • jsdom, used by jest, does not implement a number of those DOM size calculations such as clientWidth or getBoundingClientRect, instead always returning 0
  • to combat that, EuiDataGrid has a couple places where it looks for a global _isJest variable and if present skips some of those computations - EUI sets this in _scripts/jest/setup/polyfills.js_
  • even in an _isJest environment, EuiDataGrid only renders the cells visible in a 500x500 grid

This impact to testing of virtualized cells is tracked in #4470 and meant disabling a couple unit tests in Kibana while we find the right solution. #4446 tracks the wider issue of how to test any content virtualized via react-window.

There is often a hard-to-find line between unit & integration tests. My personal preference (which is not represented by EUI's setup) is to avoid almost all kinds of snapshots as they almost always test too much. In this particular case - the "user story" of _I want to test "when I click this button, a row is added to the table"_ - testing for the new row's existence within the DOM gets into testing EuiDataGrid's behaviour within the application (integration test) rather than a unit test of your app's functionality. A replacement concept could look like,

expect(wrapper.find(EuiDataGrid).props().rowCount).toBe(5);
wrapper.find('button').simulate('click');
expect(wrapper.find(EuiDataGrid).props().rowCount).toBe(6);

What's the reasoning for 500x500? Would it be possible so that EuiDataGrid, in a test environment, shows all columns and rows by default?


Oh for sure! Integration vs unit is a heated topic everywhere.
I'd argue that from the moment you render a component in a test, it becomes an integration test.

Any business logic that can be taken out of a component (is idempotent) should be, e.g. for "when I click this button, a row is added to the table", the only logic that you can unit test here is the validation of the data being added to the table. As a unit test you can't test that the correct callback is used - that's dependent on, say, EuiButton and React itself. You can't unit test that a row is added because, as you rightly pointed out, that is dependent on the behavior of the EuiDataGrid. I'd argue that your use of props() still makes it an integration test. The whole premise of React Testing Library is "to write tests that closely resemble how your web pages are used", rather than testing implementation details like props and state.

The fair and logical argument here is "just use something like cypress", and we will, but as well as, not instead of. Cypress is slow, and difficult to performantly parallelize; the runtime for testing every single edge case would be insane - I also don't want to complicate those tests with layout or css. Otherwise, why does anyone bother using React Testing Library?

What's the reasoning for 500x500? Would it be possible so that EuiDataGrid, in a test environment, shows all columns and rows by default?

I picked a round number that worked for our tests 馃槄. Thinking through this more, I think infinity should be a valid & workable value in jest. I'll test that out.

Oh for sure! Integration vs unit is a heated topic everywhere [...]

All excellent points. I commented that EUI doesn't follow my preference not as a negative to EUI, but that mine is only an opinion and there are multiple valid approaches and tooling. We'd like to have a better understanding of the various environments and provide better testing utilities and/or code practices to support them. I'd also love to learn more if you have other examples of components that have been problematic to test.

Awesome! Thank you

So far the main problem has been with EuiDataGrid but, given the complexity of the component, this was hardly a surprise.

As everything needs to be found be a role, you can't/shouldn't find elements by classnames but use accessible names instead. EuiLoadingContent has no easy way to be found as, surprisingly, it has no accessibility roles. So, I pretty much always use the following:

<EuiLoadingContent
  lines={10}
  role="progressbar"
  aria-label="loading"
  aria-busy="true"
  aria-live="polite"
/>

And the corresponding part of the test is

await waitForElementToBeRemoved(() => screen.queryAllByRole("progressbar", { name: /loading/i }))

I'm sure there's other minor tweaks like this, but none come to mind

Infinity is not a valid replacement as it is provided to css which does not support that value. However, Number.MAX_SAFE_INTEGER works in EUI's test suites. I'll do a custom build in Kibana next to see if it allows us to re-enable those jest tests.

The disable tests in Kibana are selenium, not jest, and unrelated to this (tracked in #4470). I'll queue up the change to render with Number.MAX_SAFE_INTEGER in the jest environment.

馃帀 Thank you!

I don't have time to check this works for me right now, so if I have any issues I'll raise another ticket and link it :)

Was this page helpful?
0 / 5 - 0 ratings