diff --git a/.changeset/pretty-coats-sell.md b/.changeset/pretty-coats-sell.md new file mode 100644 index 00000000000..6d55936a49e --- /dev/null +++ b/.changeset/pretty-coats-sell.md @@ -0,0 +1,5 @@ +--- +"@primer/react": minor +--- + +Add data-component attributes and associated tests for PageHeader, PageLayout, Pagehead, Popover, Portal, and ProgressBar diff --git a/packages/react/src/PageHeader/PageHeader.test.tsx b/packages/react/src/PageHeader/PageHeader.test.tsx index a66f648d4bc..0037ea0058c 100644 --- a/packages/react/src/PageHeader/PageHeader.test.tsx +++ b/packages/react/src/PageHeader/PageHeader.test.tsx @@ -20,6 +20,49 @@ describe('PageHeader', () => { implementsClassName(PageHeader.Actions, classes.Actions) implementsClassName(PageHeader.Description, classes.Description) implementsClassName(PageHeader.Navigation, classes.Navigation) + + it('renders data-component attributes for PageHeader and exported subcomponents', () => { + const {container} = render( + + ContextArea + ParentLink + ContextBar + + LeadingAction + Breadcrumbs + LeadingVisual + Title + TrailingVisual + TrailingAction + Actions + + ContextAreaActions + Description + Navigation + , + ) + + expect(container.firstChild).toHaveAttribute('data-component', 'PageHeader') + expect(container.querySelector('[data-component="PageHeader.ContextArea"]')).toBeInTheDocument() + expect(container.querySelector('[data-component="PageHeader.ParentLink"]')).toBeInTheDocument() + expect(container.querySelector('[data-component="PageHeader.ContextBar"]')).toBeInTheDocument() + expect(container.querySelector('[data-component="PageHeader.ContextAreaActions"]')).toBeInTheDocument() + expect(container.querySelector('[data-component="PageHeader.Description"]')).toBeInTheDocument() + + // New tests, but we need to update this to data-component="PageHeader.TitleArea later + expect(container.querySelector('[data-component="TitleArea"]')).toBeInTheDocument() + + // New tests, but we need to update these from PH_ to PageHeader. later + expect(container.querySelector('[data-component="PH_LeadingAction"]')).toBeInTheDocument() + expect(container.querySelector('[data-component="PH_Breadcrumbs"]')).toBeInTheDocument() + expect(container.querySelector('[data-component="PH_LeadingVisual"]')).toBeInTheDocument() + expect(container.querySelector('[data-component="PH_Title"]')).toBeInTheDocument() + expect(container.querySelector('[data-component="PH_TrailingVisual"]')).toBeInTheDocument() + expect(container.querySelector('[data-component="PH_TrailingAction"]')).toBeInTheDocument() + expect(container.querySelector('[data-component="PH_Actions"]')).toBeInTheDocument() + expect(container.querySelector('[data-component="PH_Navigation"]')).toBeInTheDocument() + }) + it('respects the title variant prop', () => { const {getByText} = render( @@ -31,6 +74,7 @@ describe('PageHeader', () => { ) expect(getByText('Title')).toHaveStyle('font-size: 32px') }) + it('renders "aria-label" prop when Navigation is rendered as "nav" landmark', () => { const {getByLabelText, getByText} = render( @@ -45,6 +89,7 @@ describe('PageHeader', () => { expect(getByLabelText('Custom')).toBeInTheDocument() expect(getByText('Navigation')).toHaveAttribute('aria-label', 'Custom') }) + it('does not render "aria-label" prop when Navigation is rendered as "div"', () => { const {getByText} = render( @@ -71,6 +116,7 @@ describe('PageHeader', () => { consoleSpy.mockRestore() }) + it('does not render "role" attribute when not explicitly specified', () => { const {container} = render( @@ -81,6 +127,7 @@ describe('PageHeader', () => { ) expect(container.firstChild).not.toHaveAttribute('role') }) + it('renders "role" attribute when explicitly specified', () => { const {container} = render( @@ -91,6 +138,7 @@ describe('PageHeader', () => { ) expect(container.firstChild).toHaveAttribute('role', 'banner') }) + it('does not render "aria-label" attribute when not explicitly specified', () => { const {container} = render( @@ -101,6 +149,7 @@ describe('PageHeader', () => { ) expect(container.firstChild).not.toHaveAttribute('aria-label') }) + it('renders custom "aria-label" attribute when explicitly specified', () => { const {container} = render( diff --git a/packages/react/src/PageHeader/PageHeader.tsx b/packages/react/src/PageHeader/PageHeader.tsx index 1acaadf06f3..59d861f5505 100644 --- a/packages/react/src/PageHeader/PageHeader.tsx +++ b/packages/react/src/PageHeader/PageHeader.tsx @@ -107,6 +107,7 @@ const Root = React.forwardRef> hidden = hiddenOnRegularAndWide, }) => { return ( - + {children} ) @@ -154,6 +159,7 @@ const ParentLink = React.forwardRef( aria-label={ariaLabel} muted className={clsx(classes.ParentLink, className)} + data-component="PageHeader.ParentLink" {...getHiddenDataAttributes(hidden)} href={href} > @@ -176,7 +182,11 @@ const ContextBar: React.FC> = ({ hidden = hiddenOnRegularAndWide, }) => { return ( - + {children} ) @@ -190,7 +200,11 @@ const ContextAreaActions: React.FC> = hidden = hiddenOnRegularAndWide, }) => { return ( - + {children} ) @@ -332,7 +346,11 @@ const Actions = ({children, className, hidden = false}: ActionsProps) => { // PageHeader.Description: The description area of the header. Visible on all viewports const Description: React.FC> = ({children, className, hidden = false}) => { return ( - + {children} ) diff --git a/packages/react/src/PageLayout/PageLayout.test.tsx b/packages/react/src/PageLayout/PageLayout.test.tsx index 08e2cb0aaad..3742d595396 100644 --- a/packages/react/src/PageLayout/PageLayout.test.tsx +++ b/packages/react/src/PageLayout/PageLayout.test.tsx @@ -103,6 +103,25 @@ describe('PageLayout', async () => { expect(getByText('Pane')).toBeVisible() }) + it('renders data-component attributes for PageLayout and exported subcomponents', () => { + const {container} = render( + + Header + Content + Pane + Sidebar + Footer + , + ) + + expect(container.querySelector('[data-component="PageLayout"]')).toBeInTheDocument() + expect(container.querySelector('[data-component="PageLayout.Header"]')).toBeInTheDocument() + expect(container.querySelector('[data-component="PageLayout.Content"]')).toBeInTheDocument() + expect(container.querySelector('[data-component="PageLayout.Pane"]')).toBeInTheDocument() + expect(container.querySelector('[data-component="PageLayout.Sidebar"]')).toBeInTheDocument() + expect(container.querySelector('[data-component="PageLayout.Footer"]')).toBeInTheDocument() + }) + it('should support labeling landmarks through `aria-label`', () => { render( diff --git a/packages/react/src/PageLayout/PageLayout.tsx b/packages/react/src/PageLayout/PageLayout.tsx index 041bb73cc6a..e447757ae82 100644 --- a/packages/react/src/PageLayout/PageLayout.tsx +++ b/packages/react/src/PageLayout/PageLayout.tsx @@ -130,6 +130,7 @@ const RootWrapper = memo( } as React.CSSProperties } className={clsx(classes.PageLayoutRoot, className)} + data-component="PageLayout" data-has-sidebar={hasSidebar || undefined} > {children} @@ -570,6 +571,7 @@ const Header: FCWithSlotMarker> = > ref={contentWrapperRef} aria-label={label} aria-labelledby={labelledBy} + data-component="PageLayout.Content" style={style} className={clsx(classes.ContentWrapper, className)} {...getResponsiveAttributes('is-hidden', hidden)} @@ -896,6 +899,7 @@ const Pane = React.forwardRef> =