Skip to content

Commit

Permalink
[EuiTabbedContent] Fix error when updated with a completely new `tabs…
Browse files Browse the repository at this point in the history
…` prop (elastic#7713)

Co-authored-by: Cee Chen <constance.chen@elastic.co>
  • Loading branch information
2 people authored and mgadewoll committed May 3, 2024
1 parent e2dbf25 commit 630dadc
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 156 deletions.
3 changes: 3 additions & 0 deletions changelogs/upcoming/7713.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
**Bug fixes**

- Fixed an `EuiTabbedContent` edge case bug that occurred when updated with a completely different set of `tabs`
Original file line number Diff line number Diff line change
@@ -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`] = `
<div>
<div
class="euiTabs emotion-euiTabs-m-bottomBorder"
role="tablist"
>
<button
aria-controls="generated-id"
aria-selected="true"
class="euiTab euiTab-isSelected emotion-euiTab-selected"
id="es"
role="tab"
type="button"
>
<span
class="euiTab__content eui-textTruncate emotion-euiTab__content-m"
>
Elasticsearch
</span>
</button>
<button
aria-controls="generated-id"
aria-selected="false"
class="euiTab kibanaTab emotion-euiTab"
data-test-subj="kibanaTab"
id="kibana"
role="tab"
type="button"
>
<span
class="euiTab__prepend"
>
prepend
</span>
<span
class="euiTab__content eui-textTruncate emotion-euiTab__content-m"
>
<strong>
Kibana
</strong>
</span>
<span
class="euiTab__append"
>
append
</span>
</button>
</div>
<div
aria-labelledby="es"
id="generated-id"
role="tabpanel"
>
<p>
Elasticsearch content
</p>
</div>
</div>
`;

exports[`EuiTabbedContent behavior when uncontrolled, the selected tab should update if it receives new content 1`] = `
<div>
<div
class="euiTabs emotion-euiTabs-m-bottomBorder"
role="tablist"
>
<button
aria-controls="generated-id"
aria-selected="false"
class="euiTab emotion-euiTab"
id="es"
role="tab"
type="button"
>
<span
class="euiTab__content eui-textTruncate emotion-euiTab__content-m"
>
Elasticsearch
</span>
</button>
<button
aria-controls="generated-id"
aria-selected="true"
class="euiTab kibanaTab euiTab-isSelected emotion-euiTab-selected"
data-test-subj="kibanaTab"
id="kibana"
role="tab"
type="button"
>
<span
class="euiTab__prepend"
>
prepend
</span>
<span
class="euiTab__content eui-textTruncate emotion-euiTab__content-m"
>
<strong>
Kibana
</strong>
</span>
<span
class="euiTab__append"
>
append
</span>
</button>
</div>
<div
aria-labelledby="kibana"
id="generated-id"
role="tabpanel"
>
<p>
updated Kibana content
</p>
</div>
</div>
`;

exports[`EuiTabbedContent is rendered with required props and tabs 1`] = `
<div
aria-label="aria-label"
Expand Down
84 changes: 55 additions & 29 deletions src/components/tabs/tabbed_content/tabbed_content.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@
*/

import React from 'react';
import { mount } from 'enzyme';
import { requiredProps, findTestSubject } from '../../../test';
import { shouldRenderCustomStyles } from '../../../test/internal';
import { fireEvent } from '@testing-library/react';
import { render } from '../../../test/rtl';
import { shouldRenderCustomStyles } from '../../../test/internal';
import { requiredProps } from '../../../test';

import { EuiTabbedContent, AUTOFOCUS } from './tabbed_content';

Expand Down Expand Up @@ -46,10 +46,10 @@ describe('EuiTabbedContent', () => {
describe('onTabClick', () => {
test('is called when a tab is clicked', () => {
const onTabClickHandler = jest.fn();
const component = mount(
const { getByTestSubject } = render(
<EuiTabbedContent onTabClick={onTabClickHandler} tabs={tabs} />
);
findTestSubject(component, 'kibanaTab').simulate('click');
fireEvent.click(getByTestSubject('kibanaTab'));
expect(onTabClickHandler).toBeCalledTimes(1);
expect(onTabClickHandler).toBeCalledWith(kibanaTab);
});
Expand Down Expand Up @@ -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(<EuiTabbedContent tabs={tabs} />);
expect(container.firstChild).toMatchSnapshot();
describe('when selectedTab state is uncontrolled', () => {
it('selects the first tab by default', () => {
const { getAllByRole } = render(<EuiTabbedContent tabs={tabs} />);
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(<EuiTabbedContent tabs={tabs} />);

component.find('EuiTab[id="kibana"] button').first().simulate('click');

component.setProps({
tabs: [
elasticsearchTab,
{
...kibanaTab,
content: <p>updated Kibana content</p>,
},
],
});
it("does not reset the existing selected tab if the tab's contents update", () => {
const { rerender, getAllByRole, getByTestSubject } = render(
<EuiTabbedContent tabs={[elasticsearchTab, { ...kibanaTab }]} />
);

fireEvent.click(getByTestSubject('kibanaTab'));
expect(getAllByRole('tab')[1]).toHaveAttribute('aria-selected', 'true');

rerender(
<EuiTabbedContent
tabs={[
elasticsearchTab,
{ ...kibanaTab, content: <p>pdated Kibana content</p> },
]}
/>
);
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(
<EuiTabbedContent tabs={tabs} />
);

fireEvent.click(getByTestSubject('kibanaTab'));
expect(getAllByRole('tab')[1]).toHaveAttribute('aria-selected', 'true');

rerender(
<EuiTabbedContent
tabs={[
{
id: 'hello',
name: 'New tab 1',
content: <p>Hello</p>,
},
{
id: 'world',
name: 'New tab 2',
content: <p>World</p>,
},
]}
/>
);
expect(getAllByRole('tab')[0]).toHaveAttribute('aria-selected', 'true');
});
});
});
12 changes: 5 additions & 7 deletions src/components/tabs/tabbed_content/tabbed_content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down Expand Up @@ -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 (
<div className={className} {...rest}>
Expand All @@ -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,
Expand Down

0 comments on commit 630dadc

Please sign in to comment.