Skip to content

Commit

Permalink
feat(Tab): make tab functional component (#9722)
Browse files Browse the repository at this point in the history
* feat(Tab): make tab functional component

* feat(Tabs): convert to functional

* fix: remove console.logs

* fix: export components as default

* fix: tabs state and refs

* fix: use prefix

* fix: add feature flag v11 story

* fix: temp use v10 classNames

* fix: clean up, comment, and fix select

* fix: keyboard navigation

* fix: remove console log

* chore: tabs comment

* feat: add tab tests

* feat: add tabs tests

* fix: add back light

* fix: remove v11 styles

* Update packages/react/src/components/Tabs/index.js

Co-authored-by: Taylor Jones <[email protected]>

* fix: next tab test and deprecation

* fix: tabs deprecate unused props

* fix: deprecations'

* fix: tab role presentation test

* fix: remove unused arg'

Co-authored-by: Taylor Jones <[email protected]>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
  • Loading branch information
3 people authored Oct 19, 2021
1 parent ac2c448 commit 7276911
Show file tree
Hide file tree
Showing 10 changed files with 1,276 additions and 10 deletions.
8 changes: 8 additions & 0 deletions packages/carbon-react/src/components/Tabs/Tabs.stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,18 @@

import React from 'react';
import { Button, Tab, Tabs, TabsSkeleton } from '../Tabs';
import { unstable_FeatureFlags as FeatureFlags } from 'carbon-components-react';
import { Layer } from '../Layer';

export default {
title: 'Components/Tabs',
decorators: [
(Story) => (
<FeatureFlags flags={{ 'enable-v11-release': true }}>
<Story />
</FeatureFlags>
),
],
parameters: {
component: Tabs,
subcomponents: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5905,7 +5905,6 @@ Map {
"label": "provide a label",
"onClick": [Function],
"onKeyDown": [Function],
"role": "presentation",
"selected": false,
},
"propTypes": Object {
Expand Down Expand Up @@ -5946,10 +5945,7 @@ Map {
"renderContent": Object {
"type": "func",
},
"role": Object {
"isRequired": true,
"type": "string",
},
"role": [Function],
"selected": Object {
"isRequired": true,
"type": "bool",
Expand Down
2 changes: 1 addition & 1 deletion packages/react/src/components/Tab/Tab-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ describe('Tab', () => {
});

it('renders <li> with [role="presentation"]', () => {
expect(wrapper.props().role).toEqual('presentation');
expect(wrapper.find('li').prop('role')).toEqual('presentation');
});

it('renders <button> with tabindex set to 0', () => {
Expand Down
4 changes: 2 additions & 2 deletions packages/react/src/components/Tab/Tab.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export default class Tab extends React.Component {
/**
* Provide an accessibility role for your Tab
*/
role: PropTypes.string.isRequired,
role: deprecate(PropTypes.string),

/**
* Whether your Tab is selected.
Expand All @@ -97,7 +97,6 @@ export default class Tab extends React.Component {
};

static defaultProps = {
role: 'presentation',
label: 'provide a label',
selected: false,
onClick: () => {},
Expand All @@ -122,6 +121,7 @@ export default class Tab extends React.Component {
renderAnchor,
renderButton,
renderContent, // eslint-disable-line no-unused-vars
role, // eslint-disable-line no-unused-vars
...other
} = this.props;

Expand Down
8 changes: 7 additions & 1 deletion packages/react/src/components/Tab/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,10 @@
* LICENSE file in the root directory of this source tree.
*/

export default from './Tab';
import * as FeatureFlags from '@carbon/feature-flags';
import { default as TabNext } from './next/Tab';
import { default as TabClassic } from './Tab';

const Tab = FeatureFlags.enabled('enable-v11-release') ? TabNext : TabClassic;

export default Tab;
204 changes: 204 additions & 0 deletions packages/react/src/components/Tab/next/Tab-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
import React from 'react';
import { default as Tab } from './Tab';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';

describe('Tab', () => {
it('adds extra classes that are passed via className', async () => {
render(
<Tab
className="custom-class"
label="Tab 1"
onClick={() => {}}
onKeyDown={() => {}}
selected={false}>
<p>Content for first tab goes here.</p>
</Tab>
);

await expect(
screen.getByRole('presentation').classList.contains('custom-class')
).toBe(true);
});

it('sets tabIndex on <button> if one is passed via props', async () => {
render(
<Tab
label="Tab 1"
// eslint-disable-next-line jsx-a11y/tabindex-no-positive
tabIndex={2}
onClick={() => {}}
onKeyDown={() => {}}
selected={false}>
<p>Content for first tab goes here.</p>
</Tab>
);

await expect(screen.getByRole('tab').tabIndex).toEqual(2);
});

it('renders <li> with [role="presentation"]', async () => {
render(
<Tab
className="custom-class"
label="Tab 1"
onClick={() => {}}
onKeyDown={() => {}}
selected={false}>
<p>Content for first tab goes here.</p>
</Tab>
);

await expect(screen.getByRole('presentation')).toBeTruthy();
});

it('renders <button> with tabindex set to 0 by default', async () => {
render(
<Tab
label="Tab 1"
onClick={() => {}}
onKeyDown={() => {}}
selected={false}>
<p>Content for first tab goes here.</p>
</Tab>
);

await expect(screen.getByRole('tab').tabIndex).toEqual(0);
});

it('renders <button> with tabindex set to -1 if disabled', async () => {
render(
<Tab
label="Tab 1"
onClick={() => {}}
onKeyDown={() => {}}
selected={false}
disabled>
<p>Content for first tab goes here.</p>
</Tab>
);

await expect(screen.getByRole('tab').tabIndex).toEqual(-1);
});

it('uses label to set children on <button> when passed via props', async () => {
render(
<Tab
label="Tab 1"
onClick={() => {}}
onKeyDown={() => {}}
selected={false}>
<p>Content for first tab goes here.</p>
</Tab>
);

await expect(screen.getByRole('tab').textContent).toBe('Tab 1');
});

it('has aria-disabled that matches disabled', async () => {
render(
<Tab
label="Tab 1"
onClick={() => {}}
onKeyDown={() => {}}
selected={false}
disabled>
<p>Content for first tab goes here.</p>
</Tab>
);

await expect(screen.getByRole('tab')).toHaveAttribute('aria-disabled');
});
});

describe('Click events', () => {
it('invokes handleTabClick from handleTabClick prop', async () => {
const handleTabClick = jest.fn();
render(
<Tab
label="Tab 1"
handleTabClick={handleTabClick}
onClick={() => {}}
onKeyDown={() => {}}
selected={false}>
<p>Content for first tab goes here.</p>
</Tab>
);

const button = screen.getByRole('tab');
userEvent.click(button);
await expect(handleTabClick).toHaveBeenCalled();
});

it('invokes onClick when a function is passed to onClick prop', async () => {
const onClick = jest.fn();

render(
<Tab
label="Tab 1"
onClick={onClick}
onKeyDown={() => {}}
selected={false}>
<p>Content for first tab goes here.</p>
</Tab>
);

const button = screen.getByRole('tab');
userEvent.click(button);
await expect(onClick).toHaveBeenCalled();
});

it('does not invoke click handler if tab is disabled', async () => {
const onClick = jest.fn();

render(
<Tab
label="Tab 1"
onClick={onClick}
onKeyDown={() => {}}
selected={false}
disabled>
<p>Content for first tab goes here.</p>
</Tab>
);

const button = screen.getByRole('tab');
userEvent.click(button);
await expect(onClick).not.toHaveBeenCalled();
});
});

describe('Keyboard events', () => {
it('invokes onKeyDown from onKeyDown prop', async () => {
const onKeyDown = jest.fn();
render(
<Tab label="Tab 1" onClick={() => {}} onKeyDown={onKeyDown} selected>
<p>Content for first tab goes here.</p>
</Tab>
);

const button = screen.getByRole('tab');
userEvent.type(button, '[ArrowLeft]');

await expect(onKeyDown).toHaveBeenCalled();
});

it('invokes handleTabKeyDown from handleTabKeyDown prop', async () => {
const handleTabKeyDown = jest.fn();
render(
<Tab
label="Tab 1"
onClick={() => {}}
onKeyDown={() => {}}
handleTabKeyDown={handleTabKeyDown}
selected>
<p>Content for first tab goes here.</p>
</Tab>
);

const button = screen.getByRole('tab');
userEvent.type(button, '[ArrowRight]');

await expect(handleTabKeyDown).toHaveBeenCalled();
});
});
Loading

0 comments on commit 7276911

Please sign in to comment.