diff --git a/packages/react/src/components/Tabs/Tabs-test.js b/packages/react/src/components/Tabs/__tests__/Tabs-test.js
similarity index 76%
rename from packages/react/src/components/Tabs/Tabs-test.js
rename to packages/react/src/components/Tabs/__tests__/Tabs-test.js
index 759921a7fe1e..e3a346a9f535 100644
--- a/packages/react/src/components/Tabs/Tabs-test.js
+++ b/packages/react/src/components/Tabs/__tests__/Tabs-test.js
@@ -7,12 +7,12 @@ import {
TabPanels,
TabList,
TabListVertical,
-} from './Tabs';
+} from '../Tabs';
import { act } from 'react';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
-import * as hooks from '../../internal/useMatchMedia';
+import * as hooks from '../../../internal/useMatchMedia';
const prefix = 'cds';
@@ -255,6 +255,78 @@ describe('Tab', () => {
expect(onKeyDown).toHaveBeenCalled();
});
+ it('should go to the next tab using arrow keys', async () => {
+ render(
+
+
+ Tab Label 1
+ Tab Label 2
+ Tab Label 3
+
+
+ Tab Panel 1
+ Tab Panel 2
+ Tab Panel 3
+
+
+ );
+
+ await userEvent.tab();
+ await userEvent.keyboard('[ArrowRight]');
+
+ expect(screen.getByTestId('tab-testid-2')).toHaveAttribute(
+ 'aria-selected',
+ 'true'
+ );
+
+ await userEvent.keyboard('[ArrowLeft]');
+
+ expect(screen.getByTestId('tab-testid-1')).toHaveAttribute(
+ 'aria-selected',
+ 'true'
+ );
+
+ await userEvent.keyboard('[End]');
+
+ expect(screen.getByTestId('tab-testid-3')).toHaveAttribute(
+ 'aria-selected',
+ 'true'
+ );
+
+ await userEvent.keyboard('[Home]');
+
+ expect(screen.getByTestId('tab-testid-1')).toHaveAttribute(
+ 'aria-selected',
+ 'true'
+ );
+ });
+
+ it('should go to the next tab with manual activation', async () => {
+ render(
+
+
+ Tab Label 1
+ Tab Label 2
+ Tab Label 3
+
+
+ Tab Panel 1
+ Tab Panel 2
+ Tab Panel 3
+
+
+ );
+
+ await userEvent.tab();
+ await userEvent.keyboard('[ArrowRight]');
+ await userEvent.keyboard('[Space]');
+
+ expect(screen.getByTestId('tab-testid-2')).toHaveAttribute(
+ 'aria-selected',
+ 'true'
+ );
+ });
+
it('should render close icon if dismissable', () => {
render(
{}}>
@@ -278,6 +350,36 @@ describe('Tab', () => {
).not.toHaveClass(`${prefix}--visually-hidden`);
});
+ it('should ignore hover on dismissable icon if it is a contained tab', async () => {
+ render(
+ {}}>
+
+ Tab Label 1
+
+ Tab Label 2
+
+ Tab Label 3
+
+
+ Tab Panel 1
+ Tab Panel 2
+ Tab Panel 3
+
+
+ );
+
+ const tab = screen.getByTestId('tab-testid');
+ const tabIcon = screen.getAllByLabelText(
+ 'Press delete to remove Tab Label 2 tab'
+ )[0].parentElement;
+
+ await userEvent.hover(tabIcon);
+ expect(tab).toHaveClass('cds--tabs__nav-item--hover-off');
+
+ await userEvent.unhover(tabIcon);
+ expect(tab).not.toHaveClass('cds--tabs__nav-item--hover-off');
+ });
+
it('should not render close icon if not dismissable', () => {
render(
@@ -463,6 +565,136 @@ describe('Tab', () => {
});
});
+describe('TabsVertical', () => {
+ beforeEach(() => {
+ jest.resetModules();
+ jest.spyOn(hooks, 'useMatchMedia').mockImplementation(() => false);
+ });
+
+ it('should render as horizontal tab in sm breakpoint', () => {
+ jest.spyOn(hooks, 'useMatchMedia').mockImplementation(() => true);
+
+ const { container } = render(
+
+
+ Tab Label 1
+ Tab Label 2
+ Tab Label 3
+
+
+ Tab Panel 1
+ Tab Panel 2
+ Tab Panel 3
+
+
+ );
+
+ expect(container.firstChild).not.toHaveClass(`${prefix}--tabs--vertical`);
+ });
+
+ it('should go to the next tab using arrow keys', async () => {
+ render(
+
+
+ Tab Label 1
+ Tab Label 2
+ Tab Label 3
+
+
+ Tab Panel 1
+ Tab Panel 2
+ Tab Panel 3
+
+
+ );
+
+ await userEvent.tab();
+ await userEvent.keyboard('[ArrowDown]');
+
+ expect(screen.getByTestId('tab-testid-2')).toHaveAttribute(
+ 'aria-selected',
+ 'true'
+ );
+
+ await userEvent.keyboard('[ArrowUp]');
+
+ expect(screen.getByTestId('tab-testid-1')).toHaveAttribute(
+ 'aria-selected',
+ 'true'
+ );
+
+ await userEvent.keyboard('[End]');
+
+ expect(screen.getByTestId('tab-testid-3')).toHaveAttribute(
+ 'aria-selected',
+ 'true'
+ );
+
+ await userEvent.keyboard('[Home]');
+
+ expect(screen.getByTestId('tab-testid-1')).toHaveAttribute(
+ 'aria-selected',
+ 'true'
+ );
+ });
+
+ it('should go to the next tab with manual activation', async () => {
+ render(
+
+
+ Tab Label 1
+ Tab Label 2
+ Tab Label 3
+
+
+ Tab Panel 1
+ Tab Panel 2
+ Tab Panel 3
+
+
+ );
+
+ await userEvent.tab();
+ await userEvent.keyboard('[ArrowDown]');
+ await userEvent.keyboard('[Space]');
+
+ expect(screen.getByTestId('tab-testid-2')).toHaveAttribute(
+ 'aria-selected',
+ 'true'
+ );
+ });
+
+ it('should not select a disabled tab and select next tab', () => {
+ render(
+
+
+
+ Tab Label 1
+
+ Tab Label 2
+ Tab Label 3
+
+
+ Tab Panel 1
+ Tab Panel 2
+ Tab Panel 3
+
+
+ );
+
+ expect(screen.getByTestId('tab-testid-1')).toHaveAttribute(
+ 'aria-selected',
+ 'false'
+ );
+
+ // By default, if a Tab is disabled, the next Tab should be selected
+ expect(screen.getByTestId('tab-testid-2')).toHaveAttribute(
+ 'aria-selected',
+ 'true'
+ );
+ });
+});
+
describe('TabPanel', () => {
beforeEach(() => {
jest.resetModules();
diff --git a/packages/react/src/components/Tabs/__tests__/Tabs.Skeleton-test.js b/packages/react/src/components/Tabs/__tests__/Tabs.Skeleton-test.js
new file mode 100644
index 000000000000..e295681d5a05
--- /dev/null
+++ b/packages/react/src/components/Tabs/__tests__/Tabs.Skeleton-test.js
@@ -0,0 +1,22 @@
+/**
+ * Copyright IBM Corp. 2016, 2023
+ *
+ * This source code is licensed under the Apache-2.0 license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+import React from 'react';
+import { TabsSkeleton } from '../Tabs.Skeleton';
+import { render } from '@testing-library/react';
+
+describe('TabsSkeleton', () => {
+ it('should support a custom `className` prop on the outermost element', () => {
+ const { container } = render();
+ expect(container.firstChild).toHaveClass('test');
+ });
+
+ it('should spread additional props on the outermost element', () => {
+ const { container } = render();
+ expect(container.firstChild).toHaveAttribute('data-testid', 'test');
+ });
+});
diff --git a/packages/react/src/components/Tabs/usePressable.js b/packages/react/src/components/Tabs/usePressable.js
index baf12d70ad70..fcf32b31c3ca 100644
--- a/packages/react/src/components/Tabs/usePressable.js
+++ b/packages/react/src/components/Tabs/usePressable.js
@@ -5,6 +5,8 @@
* LICENSE file in the root directory of this source tree.
*/
+/* istanbul ignore file */
+
import { useEffect, useRef, useState } from 'react';
/**