kenshoo / react-multi-select

A Multi Select component built with and for React
https://kenshoo.github.io/react-multi-select
MIT License
120 stars 53 forks source link

Items do not render in tests (using jest and testing-library/react) #200

Open jake-miles opened 2 years ago

jake-miles commented 2 years ago

Expected Behavior

When a test written with jest and testing-library/react renders a MultiSelect component with a non-empty list of items, the items are rendered in the MultiSelect component, i.e. appear in the result of testing-library's screen.debug and are queryable using testing-library dom element queries.

Actual Behavior

The rendered component does not include the items in the rendered DOM, although it does include the number selected (e.g. "1 selected") in the selection list component, implying that the component's state is up-to-date, and something about the test environment or MultiSelect configuration is preventing the items themselves from rendering.

Steps to Reproduce the Problem

  1. In package.json (among other things - I believe these are the relevant packages)
    "@kenshooui/react-multi-select": "1.1.6",  
     "react": "16.11.0",
    "jest-cli": "24.9.0",
    "@testing-library/dom": "8.11.1",
    "@testing-library/jest-dom": "5.16.1",
    "@testing-library/react": "12.1.2",
  2. Test:

    import MultiSelect from '@kenshooui/react-multi-select'; import { render, screen } from '@testing-library/react';

    describe("testing multiselect rendering", () => { it.only("renders something in the id list", async () => {

    const items = [{ id: 'xyz1', label: 'xyz1' }]; let selected = items;

    const setSelected = ids => selected = ids;

    await render(<MultiSelect name="selected_items" items={items} selectedItems={selected} onChange={setSelected} />);

    // would normally use getBy or findBy - using queryBy to more easily demo the issue const numSelected = screen.queryByText("1 selected"); expect(numSelected).not.toBeNull(); // => assertion passes

    const item = screen.queryByText("xyz1"); expect(item).not.toBeNull(); => // assertion fails }); });

The first assertion passes and the second one fails, i.e. "1 selected" is in the rendered DOM, implying that the component's state is up to date, but the item's label "xyz1" is not, meaning that the selected item itself isn't rendering.

Likewise, the result of screen.debug() shows:

                <div
                  class="kn-selection_status__status___1qxE9"
                >
                  1 selected
                </div>

which is the element in the target list on the right that reports the number of selected items in it, and includes the list component rendered, but no items within it:

              <div
                style="overflow: visible; height: 0px; width: 0px;"
              >
                <div
                  aria-label="grid"
                  aria-readonly="true"
                  class="ReactVirtualized__Grid ReactVirtualized__List kn-list__list___22Wuc"
                  role="grid"
                  style="box-sizing: border-box; direction: ltr; height: -47px; position: relative; width: 0px; will-change: transform; overflow-x: hidden; overflow-y: auto;"
                  tabindex="0"
                />
              </div>
            </div>

Likewise, the source list on the left renders its top-level grid component, but no items:

                <div
                  class="kn-item_label__label___2fJ5p"
                >
                  Select All
                </div>
              </div>
              <div
                style="overflow: visible; height: 0px; width: 0px;"
              >
                <div
                  aria-label="grid"
                  aria-readonly="true"
                  class="ReactVirtualized__Grid ReactVirtualized__List kn-list__list___22Wuc"
                  role="grid"
                  style="box-sizing: border-box; direction: ltr; height: -87px; position: relative; width: -1px; will-change: transform; overflow-x: hidden; overflow-y: auto;"
                  tabindex="0"
                />
              </div>

When I inspect the DOM in a browser, those list components contains other elements rendering the list items.

Is this a missing configuration option? Perhaps something rendering itself differently based on device that isn't rendering in the test environment's dom?

My end goal is to write tests confirming that actions taken outside of this component in the surrounding page are updating this component's state correctly (i.e. my end goal is not to test MutiSelect itself, but that the set of currently-selected items is being propagated to it correctly, because other components in the page can also change the selection).

Thanks for any help with this.

Specifications

ttlekich commented 1 year ago

Spent some time on this thinking it was only react-virtualized's 'AutoSizer' that was causing issues. However, it seems like the auto height higher order component throws in some issues too. Using these spies, I was able to render items in the dropdown:

const height = 100 // set to whatever you need
const width = 100 // set to whatever you need
jest.spyOn(HTMLElement.prototype, 'offsetWidth', 'get').mockReturnValue(
    width
)
jest.spyOn(
    HTMLElement.prototype,
    'clientHeight',
    'get'
).mockImplementation(() => height)

For reference,

https://github.com/kenshoo/react-multi-select/blob/master/src/components/with_responsive_height.js#L52 and (AutoSizer) https://github.com/kenshoo/react-multi-select/blob/master/src/components/list/items_list.js#L69