diff --git a/changelogs/upcoming/7713.md b/changelogs/upcoming/7713.md new file mode 100644 index 00000000000..1f53a7e94ff --- /dev/null +++ b/changelogs/upcoming/7713.md @@ -0,0 +1,3 @@ +**Bug fixes** + +- Fixed an `EuiTabbedContent` edge case bug that occurred when updated with a completely different set of `tabs` diff --git a/src/components/tabs/tabbed_content/__snapshots__/tabbed_content.test.tsx.snap b/src/components/tabs/tabbed_content/__snapshots__/tabbed_content.test.tsx.snap index 7d16b9bd716..374403f8198 100644 --- a/src/components/tabs/tabbed_content/__snapshots__/tabbed_content.test.tsx.snap +++ b/src/components/tabs/tabbed_content/__snapshots__/tabbed_content.test.tsx.snap @@ -1,125 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`EuiTabbedContent behavior when selected tab state isn't controlled by the owner, select the first tab by default 1`] = ` -
-
- - -
-
-

- Elasticsearch content -

-
-
-`; - -exports[`EuiTabbedContent behavior when uncontrolled, the selected tab should update if it receives new content 1`] = ` -
-
- - -
-
-

- updated Kibana content -

-
-
-`; - exports[`EuiTabbedContent is rendered with required props and tabs 1`] = `
{ describe('onTabClick', () => { test('is called when a tab is clicked', () => { const onTabClickHandler = jest.fn(); - const component = mount( + const { getByTestSubject } = render( ); - findTestSubject(component, 'kibanaTab').simulate('click'); + fireEvent.click(getByTestSubject('kibanaTab')); expect(onTabClickHandler).toBeCalledTimes(1); expect(onTabClickHandler).toBeCalledWith(kibanaTab); }); @@ -94,33 +94,59 @@ describe('EuiTabbedContent', () => { }); describe('behavior', () => { - test("when selected tab state isn't controlled by the owner, select the first tab by default", () => { - const { container } = render(); - expect(container.firstChild).toMatchSnapshot(); + describe('when selectedTab state is uncontrolled', () => { + it('selects the first tab by default', () => { + const { getAllByRole } = render(); + const tabElements = getAllByRole('tab'); + expect(tabElements[0]).toHaveAttribute('aria-selected', 'true'); + expect(tabElements[1]).toHaveAttribute('aria-selected', 'false'); + }); }); - test('when uncontrolled, the selected tab should update if it receives new content', () => { - const tabs = [ - elasticsearchTab, - { - ...kibanaTab, - }, - ]; - const component = mount(); - - component.find('EuiTab[id="kibana"] button').first().simulate('click'); - - component.setProps({ - tabs: [ - elasticsearchTab, - { - ...kibanaTab, - content:

updated Kibana content

, - }, - ], - }); + it("does not reset the existing selected tab if the tab's contents update", () => { + const { rerender, getAllByRole, getByTestSubject } = render( + + ); + + fireEvent.click(getByTestSubject('kibanaTab')); + expect(getAllByRole('tab')[1]).toHaveAttribute('aria-selected', 'true'); + + rerender( + pdated Kibana content

}, + ]} + /> + ); + expect(getAllByRole('tab')[1]).toHaveAttribute('aria-selected', 'true'); + }); - expect(component.render()).toMatchSnapshot(); + it('resets the selected tab to the first if the `tabs` content completely changes', () => { + const { rerender, getAllByRole, getByTestSubject } = render( + + ); + + fireEvent.click(getByTestSubject('kibanaTab')); + expect(getAllByRole('tab')[1]).toHaveAttribute('aria-selected', 'true'); + + rerender( + Hello

, + }, + { + id: 'world', + name: 'New tab 2', + content:

World

, + }, + ]} + /> + ); + expect(getAllByRole('tab')[0]).toHaveAttribute('aria-selected', 'true'); }); }); }); diff --git a/src/components/tabs/tabbed_content/tabbed_content.tsx b/src/components/tabs/tabbed_content/tabbed_content.tsx index 99ed28a5bcc..7a81e25ea25 100644 --- a/src/components/tabs/tabbed_content/tabbed_content.tsx +++ b/src/components/tabs/tabbed_content/tabbed_content.tsx @@ -86,8 +86,7 @@ export class EuiTabbedContent extends Component< // Only track selection state if it's not controlled externally. let selectedTabId; if (!selectedTab) { - selectedTabId = - (initialSelectedTab && initialSelectedTab.id) || tabs[0].id; + selectedTabId = initialSelectedTab?.id || tabs[0].id; } this.state = { @@ -154,11 +153,10 @@ export class EuiTabbedContent extends Component< // Allow the consumer to control tab selection. const selectedTab = externalSelectedTab || - tabs.find( - (tab: EuiTabbedContentTab) => tab.id === this.state.selectedTabId - ); + tabs.find((tab) => tab.id === this.state.selectedTabId) || + tabs[0]; // Fall back to the first tab if a selected tab can't be found - const { content: selectedTabContent, id: selectedTabId } = selectedTab!; + const { content: selectedTabContent, id: selectedTabId } = selectedTab; return (
@@ -169,7 +167,7 @@ export class EuiTabbedContent extends Component< onFocus={this.initializeFocus} onBlur={this.removeFocus} > - {tabs.map((tab: EuiTabbedContentTab) => { + {tabs.map((tab) => { const { id, name,