diff --git a/e2e/components/Slug/Slug-test.avt.e2e.js b/e2e/components/Slug/Slug-test.avt.e2e.js index 7505b18527ad..7da0e7057c96 100644 --- a/e2e/components/Slug/Slug-test.avt.e2e.js +++ b/e2e/components/Slug/Slug-test.avt.e2e.js @@ -10,7 +10,7 @@ const { expect, test } = require('@playwright/test'); const { visitStory } = require('../../test-utils/storybook'); -test.describe('Slug @avt', () => { +test.describe('@avt Slug', async () => { test('@avt-default-state', async ({ page }) => { await visitStory(page, { component: 'Slug', @@ -47,4 +47,54 @@ test.describe('Slug @avt', () => { }); await expect(page).toHaveNoACViolations('Slug-form'); }); + + test('@avt-keyboard-nav - slug', async ({ page }) => { + await visitStory(page, { + component: 'Search', + id: 'experimental-unstable-slug--callout', + globals: { + theme: 'white', + }, + }); + const slug = page.getByRole('button', { + name: 'AI - Show information', + }); + const callout = page.locator('.cds--popover--open'); + await expect(slug).toBeVisible(); + await expect(callout).toBeVisible(); + + // Tab to the Slug + await page.keyboard.press('Tab'); + await expect(slug).toBeFocused(); + + // Close the slug (example is open by default) + await page.keyboard.press('Enter'); + await expect(callout).toBeHidden(); + + // Should also be able to open with space + await page.keyboard.press('Space'); + await expect(callout).toBeVisible(); + + // Tab should go to buttons, and then close after last button + await page.keyboard.press('Tab'); + await page.keyboard.press('Tab'); + await page.keyboard.press('Tab'); + await page.keyboard.press('Tab'); + await expect( + page.getByRole('button', { + name: 'View details', + }) + ).toBeFocused(); + await page.keyboard.press('Tab'); + await expect(callout).toBeHidden(); + + // Should also close on escape + await page.keyboard.press('Shift+Tab'); + await page.keyboard.press('Shift+Tab'); + await expect(callout).toBeHidden(); + await slug.click(); + await expect(callout).toBeVisible(); + await page.keyboard.press('Escape'); + await expect(callout).toBeHidden(); + }); }); diff --git a/packages/react/src/components/Checkbox/__tests__/Checkbox-test.js b/packages/react/src/components/Checkbox/__tests__/Checkbox-test.js index 49938f3a5a20..a92359c4e473 100644 --- a/packages/react/src/components/Checkbox/__tests__/Checkbox-test.js +++ b/packages/react/src/components/Checkbox/__tests__/Checkbox-test.js @@ -9,6 +9,9 @@ import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import React from 'react'; import Checkbox from '../Checkbox'; +import { Slug } from '../../Slug'; + +const prefix = 'cds'; describe('Checkbox', () => { it('should set the `id` on the element', () => { @@ -57,7 +60,9 @@ describe('Checkbox', () => { it('should hide the label if hideLabel is provided as a prop', () => { render(); - expect(screen.getByText('test-label')).toHaveClass('cds--visually-hidden'); + expect(screen.getByText('test-label')).toHaveClass( + `${prefix}--visually-hidden` + ); }); it('should render helperText', () => { @@ -113,7 +118,9 @@ describe('Checkbox', () => { /> ); - expect(container.firstChild).toHaveClass(`cds--checkbox-wrapper--readonly`); + expect(container.firstChild).toHaveClass( + `${prefix}--checkbox-wrapper--readonly` + ); }); it('should respect warn prop', () => { @@ -128,10 +135,12 @@ describe('Checkbox', () => { // eslint-disable-next-line testing-library/no-node-access, testing-library/no-container const warnIcon = container.querySelector( - `svg.cds--checkbox__invalid-icon--warning` + `svg.${prefix}--checkbox__invalid-icon--warning` ); - expect(container.firstChild).toHaveClass(`cds--checkbox-wrapper--warning`); + expect(container.firstChild).toHaveClass( + `${prefix}--checkbox-wrapper--warning` + ); expect(warnIcon).toBeInTheDocument(); }); @@ -147,7 +156,9 @@ describe('Checkbox', () => { ); expect(screen.getByText('Warn text')).toBeInTheDocument(); - expect(screen.getByText('Warn text')).toHaveClass(`cds--form-requirement`); + expect(screen.getByText('Warn text')).toHaveClass( + `${prefix}--form-requirement` + ); }); it('should call the `onChange` prop when the value changes', async () => { @@ -187,4 +198,19 @@ describe('Checkbox', () => { expect(onClick).toHaveBeenCalled(); expect(onChange).not.toHaveBeenCalled(); }); + + it('should respect slug prop', () => { + const { container } = render( + } + /> + ); + + expect(container.firstChild).toHaveClass( + `${prefix}--checkbox-wrapper--slug` + ); + }); }); diff --git a/packages/react/src/components/CheckboxGroup/CheckboxGroup-test.js b/packages/react/src/components/CheckboxGroup/CheckboxGroup-test.js index ff0d65540b4b..46b0f6c7d439 100644 --- a/packages/react/src/components/CheckboxGroup/CheckboxGroup-test.js +++ b/packages/react/src/components/CheckboxGroup/CheckboxGroup-test.js @@ -9,6 +9,7 @@ import React from 'react'; import CheckboxGroup from '../CheckboxGroup'; import Checkbox from '../Checkbox/Checkbox'; import { render, screen } from '@testing-library/react'; +import { Slug } from '../Slug'; const prefix = 'cds'; @@ -148,4 +149,16 @@ describe('CheckboxGroup', () => { `${prefix}--form-requirement` ); }); + + it('should respect slug prop', () => { + const { container } = render( + } + /> + ); + + expect(container.firstChild).toHaveClass(`${prefix}--checkbox-group--slug`); + }); }); diff --git a/packages/react/src/components/ComboBox/ComboBox-test.js b/packages/react/src/components/ComboBox/ComboBox-test.js index 2a474364847e..7aa22d7d3203 100644 --- a/packages/react/src/components/ComboBox/ComboBox-test.js +++ b/packages/react/src/components/ComboBox/ComboBox-test.js @@ -17,12 +17,15 @@ import { } from '../ListBox/test-helpers'; import ComboBox from '../ComboBox'; import { act } from 'react-dom/test-utils'; +import { Slug } from '../Slug'; const findInputNode = () => screen.getByRole('combobox'); const openMenu = async () => { await userEvent.click(findInputNode()); }; +const prefix = 'cds'; + describe('ComboBox', () => { let mockProps; @@ -117,6 +120,14 @@ describe('ComboBox', () => { expect(findInputNode()).toHaveDisplayValue('Apple'); }); + it('should respect slug prop', () => { + const { container } = render(} />); + + expect(container.firstChild).toHaveClass( + `${prefix}--list-box__wrapper--slug` + ); + }); + describe('should display initially selected item found in `initialSelectedItem`', () => { it('using an object type for the `initialSelectedItem` prop', () => { render( @@ -176,7 +187,9 @@ describe('ComboBox', () => { it('should not let the user expand the menu', async () => { render(); await openMenu(); - expect(findListBoxNode()).not.toHaveClass('cds--list-box--expanded'); + expect(findListBoxNode()).not.toHaveClass( + `${prefix}--list-box--expanded` + ); }); }); @@ -196,7 +209,9 @@ describe('ComboBox', () => { it('should not let the user expand the menu', async () => { render(); await openMenu(); - expect(findListBoxNode()).not.toHaveClass('cds--list-box--expanded'); + expect(findListBoxNode()).not.toHaveClass( + `${prefix}--list-box--expanded` + ); }); }); diff --git a/packages/react/src/components/ComposedModal/ComposedModal-test.js b/packages/react/src/components/ComposedModal/ComposedModal-test.js index b4a58a80c16c..a61fa2f87aa1 100644 --- a/packages/react/src/components/ComposedModal/ComposedModal-test.js +++ b/packages/react/src/components/ComposedModal/ComposedModal-test.js @@ -14,6 +14,9 @@ import ComposedModal, { ModalBody } from './ComposedModal'; import { ModalHeader } from './ModalHeader'; import { ModalFooter } from './ModalFooter'; import { TextInput } from '../../'; +import { Slug } from '../Slug'; + +const prefix = 'cds'; describe('ComposedModal', () => { describe('it renders as expected', () => { @@ -206,7 +209,7 @@ describe('ComposedModal', () => { ); expect(screen.getByRole('dialog', { hidden: true })).toHaveClass( - 'cds--modal-container--lg' + `${prefix}--modal-container--lg` ); }); @@ -229,5 +232,21 @@ describe('ComposedModal', () => { ).toBeDisabled(); expect(screen.getByRole('button', { name: 'Cancel' })).toBeDisabled(); }); + + it('should respect slug prop', () => { + const { container } = render( + }> + Modal header + This is the modal body content + + + ); + + expect(container.firstChild).toHaveClass(`${prefix}--modal--slug`); + }); }); }); diff --git a/packages/react/src/components/DatePicker/DatePicker-test.js b/packages/react/src/components/DatePicker/DatePicker-test.js index 84b8cb6778c3..d252cb2376e9 100644 --- a/packages/react/src/components/DatePicker/DatePicker-test.js +++ b/packages/react/src/components/DatePicker/DatePicker-test.js @@ -10,6 +10,9 @@ import DatePicker from './DatePicker'; import DatePickerInput from '../DatePickerInput'; import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import { Slug } from '../Slug'; + +const prefix = 'cds'; describe('DatePicker', () => { it('should add extra classes that are passed via className', () => { @@ -51,7 +54,7 @@ describe('DatePicker', () => { expect( // eslint-disable-next-line testing-library/no-node-access - document.querySelector('.cds--date-picker--simple') + document.querySelector(`.${prefix}--date-picker--simple`) ).toBeInTheDocument(); }); @@ -71,7 +74,7 @@ describe('DatePicker', () => { expect( // eslint-disable-next-line testing-library/no-node-access - document.querySelector('.cds--date-picker--single') + document.querySelector(`.${prefix}--date-picker--single`) ).toBeInTheDocument(); }); @@ -93,7 +96,7 @@ describe('DatePicker', () => { expect( // eslint-disable-next-line testing-library/no-node-access - document.querySelector('.cds--date-picker--range') + document.querySelector(`.${prefix}--date-picker--range`) ).toBeInTheDocument(); }); @@ -161,6 +164,20 @@ describe('DatePicker', () => { expect(ref).toHaveBeenCalledWith(container.firstChild); }); + + it('should respect slug prop', () => { + render( + } + /> + ); + + expect(screen.getByRole('button')).toHaveClass(`${prefix}--slug__button`); + }); }); describe('Simple date picker', () => { @@ -272,7 +289,7 @@ describe('Single date picker', () => { ); // eslint-disable-next-line testing-library/no-node-access - const input = document.querySelector('.cds--date-picker__input'); + const input = document.querySelector(`.${prefix}--date-picker__input`); expect(screen.getByRole('application')).not.toHaveClass('open'); await userEvent.click(input); diff --git a/packages/react/src/components/Dropdown/Dropdown-test.js b/packages/react/src/components/Dropdown/Dropdown-test.js index c8c4477e1b74..bfe9a8a8560e 100644 --- a/packages/react/src/components/Dropdown/Dropdown-test.js +++ b/packages/react/src/components/Dropdown/Dropdown-test.js @@ -17,6 +17,7 @@ import { } from '../ListBox/test-helpers'; import Dropdown from '../Dropdown'; import DropdownSkeleton from '../Dropdown/Dropdown.Skeleton'; +import { Slug } from '../Slug'; const prefix = 'cds'; @@ -189,6 +190,13 @@ describe('Dropdown', () => { render(); expect(ref.current).toHaveAttribute('aria-haspopup', 'listbox'); }); + + it('should respect slug prop', () => { + const { container } = render(} />); + expect(container.firstChild).toHaveClass( + `${prefix}--list-box__wrapper--slug` + ); + }); }); }); diff --git a/packages/react/src/components/Modal/Modal-test.js b/packages/react/src/components/Modal/Modal-test.js index 31b41f8e1b6d..7e0f65d859c1 100644 --- a/packages/react/src/components/Modal/Modal-test.js +++ b/packages/react/src/components/Modal/Modal-test.js @@ -11,6 +11,9 @@ import userEvent from '@testing-library/user-event'; import Modal from './Modal'; import TextInput from '../TextInput'; +import { Slug } from '../Slug'; + +const prefix = 'cds'; describe('Modal', () => { it('should add extra classes that are passed via className', () => { @@ -86,7 +89,7 @@ describe('Modal', () => { ); - expect(screen.getByTestId('modal-2')).toHaveClass('cds--modal-tall'); + expect(screen.getByTestId('modal-2')).toHaveClass(`${prefix}--modal-tall`); }); it('should be a passive modal when passiveModal is passed', () => { @@ -105,7 +108,9 @@ describe('Modal', () => { ); - expect(screen.getByTestId('modal-3')).not.toHaveClass('cds--modal-tall'); + expect(screen.getByTestId('modal-3')).not.toHaveClass( + `${prefix}--modal-tall` + ); }); it('should set id if one is passed via props', () => { @@ -146,11 +151,10 @@ describe('Modal', () => { ); - // eslint-disable-next-line testing-library/no-node-access - expect(document.querySelector('.cds--modal-close__icon')).toHaveAttribute( - 'aria-hidden', - 'true' - ); + expect( + // eslint-disable-next-line testing-library/no-node-access + document.querySelector(`.${prefix}--modal-close__icon`) + ).toHaveAttribute('aria-hidden', 'true'); }); it('should not make the icon tabbable', () => { @@ -169,11 +173,10 @@ describe('Modal', () => { ); - // eslint-disable-next-line testing-library/no-node-access - expect(document.querySelector('.cds--modal-close__icon')).toHaveAttribute( - 'focusable', - 'false' - ); + expect( + // eslint-disable-next-line testing-library/no-node-access + document.querySelector(`.${prefix}--modal-close__icon`) + ).toHaveAttribute('focusable', 'false'); }); it('enables primary button by default', () => { @@ -328,9 +331,11 @@ describe('Modal', () => { ); - expect(screen.getByTestId('modal-5')).toHaveClass('cds--modal--danger'); + expect(screen.getByTestId('modal-5')).toHaveClass( + `${prefix}--modal--danger` + ); expect(screen.getByText('Danger button text')).toHaveClass( - 'cds--btn--danger' + `${prefix}--btn--danger` ); }); @@ -362,6 +367,19 @@ describe('Modal', () => { ).toBeDisabled(); expect(screen.getByRole('button', { name: 'Cancel' })).toBeDisabled(); }); + + it('should respect slug prop', () => { + const { container } = render( + } + /> + ); + + expect(container.firstChild).toHaveClass(`${prefix}--modal--slug`); + }); }); describe('events', () => { diff --git a/packages/react/src/components/MultiSelect/__tests__/FilterableMultiSelect-test.js b/packages/react/src/components/MultiSelect/__tests__/FilterableMultiSelect-test.js index cc24a54f2471..9a4185a207cc 100644 --- a/packages/react/src/components/MultiSelect/__tests__/FilterableMultiSelect-test.js +++ b/packages/react/src/components/MultiSelect/__tests__/FilterableMultiSelect-test.js @@ -16,6 +16,9 @@ import { generateItems, generateGenericItem, } from '../../ListBox/test-helpers'; +import { Slug } from '../../Slug'; + +const prefix = 'cds'; const openMenu = async () => { await userEvent.click(screen.getByRole('combobox')); @@ -147,4 +150,13 @@ describe('FilterableMultiSelect', () => { expect(screen.getByPlaceholderText('test')).toHaveDisplayValue(3); }); + + it('should respect slug prop', () => { + const { container } = render( + } /> + ); + expect(container.firstChild).toHaveClass( + `${prefix}--list-box__wrapper--slug` + ); + }); }); diff --git a/packages/react/src/components/MultiSelect/__tests__/MultiSelect-test.js b/packages/react/src/components/MultiSelect/__tests__/MultiSelect-test.js index cd62ae6c0621..882299e15c49 100644 --- a/packages/react/src/components/MultiSelect/__tests__/MultiSelect-test.js +++ b/packages/react/src/components/MultiSelect/__tests__/MultiSelect-test.js @@ -11,6 +11,9 @@ import React from 'react'; import MultiSelect from '../'; import { generateItems, generateGenericItem } from '../../ListBox/test-helpers'; import userEvent from '@testing-library/user-event'; +import { Slug } from '../../Slug'; + +const prefix = 'cds'; describe('MultiSelect', () => { beforeEach(() => { @@ -494,5 +497,16 @@ describe('MultiSelect', () => { render(); expect(ref.current).toHaveAttribute('aria-haspopup', 'listbox'); }); + + it('should respect slug prop', () => { + const items = generateItems(4, generateGenericItem); + const label = 'test-label'; + const { container } = render( + } /> + ); + expect(container.firstChild).toHaveClass( + `${prefix}--list-box__wrapper--slug` + ); + }); }); }); diff --git a/packages/react/src/components/NumberInput/__tests__/NumberInput-test.js b/packages/react/src/components/NumberInput/__tests__/NumberInput-test.js index 5c36402ef22a..cf78fff9c691 100644 --- a/packages/react/src/components/NumberInput/__tests__/NumberInput-test.js +++ b/packages/react/src/components/NumberInput/__tests__/NumberInput-test.js @@ -11,6 +11,7 @@ import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import React from 'react'; import { NumberInput } from '../NumberInput'; +import { Slug } from '../../Slug'; function translateWithId(id) { if (id === 'increment.number') { @@ -68,6 +69,14 @@ describe('NumberInput', () => { expect(screen.getByLabelText('test-label')).toHaveValue(5); }); + it('should respect slug prop', () => { + render(} />); + + expect( + screen.getByRole('button', { name: 'AI - Show information' }) + ).toBeInTheDocument(); + }); + it('should allow an empty string as input to the underlying ', () => { render( { it('should render an input with type="radio"', () => { @@ -132,4 +135,19 @@ describe('RadioButton', () => { ); expect(ref).toHaveBeenCalledWith(screen.getByRole('radio')); }); + + it('should respect slug prop', () => { + const { container } = render( + } + /> + ); + + expect(container.firstChild).toHaveClass( + `${prefix}--radio-button-wrapper--slug` + ); + }); }); diff --git a/packages/react/src/components/RadioButtonGroup/RadioButtonGroup-test.js b/packages/react/src/components/RadioButtonGroup/RadioButtonGroup-test.js index 891f1bec5c67..f34b8be83d69 100644 --- a/packages/react/src/components/RadioButtonGroup/RadioButtonGroup-test.js +++ b/packages/react/src/components/RadioButtonGroup/RadioButtonGroup-test.js @@ -10,6 +10,9 @@ import userEvent from '@testing-library/user-event'; import React from 'react'; import RadioButtonGroup from './RadioButtonGroup'; import RadioButton from '../RadioButton'; +import { Slug } from '../Slug'; + +const prefix = 'cds'; describe('RadioButtonGroup', () => { it('should render `legendText` in a