Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
213 changes: 103 additions & 110 deletions packages/react/src/ActionBar/ActionBar.test.tsx

Large diffs are not rendered by default.

36 changes: 20 additions & 16 deletions packages/react/src/ActionList/ActionList.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {page, userEvent} from 'vitest/browser'
import {describe, it, expect, vi} from 'vitest'
import {render as HTMLRender} from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import {render as HTMLRender, act, fireEvent} from '@testing-library/react'
import {ActionList} from '.'
import {createRef} from 'react'
import {ActionListContainerContext} from './ActionListContainerContext'
Expand Down Expand Up @@ -50,23 +50,25 @@ describe('ActionList', () => {
)

await userEvent.tab() // tab into the story, this should focus on the first button
expect(document.activeElement).toHaveTextContent('Option 1')
await expect.element(page.getByRole('option', {name: 'Option 1', exact: true})).toHaveFocus()

await userEvent.keyboard('{ArrowDown}')
expect(document.activeElement).toHaveTextContent('Option 2')
await expect.element(page.getByRole('option', {name: 'Option 2', exact: true})).toHaveFocus()

await userEvent.keyboard('{ArrowDown}')
expect(document.activeElement).toHaveTextContent('Option 3')
await expect.element(page.getByRole('option', {name: 'Option 3', exact: true})).toHaveFocus()

await userEvent.keyboard('{ArrowDown}')
expect(document.activeElement).toHaveTextContent('Option 4')
await expect.element(page.getByRole('option', {name: 'Option 4', exact: true})).toHaveFocus()

await userEvent.keyboard('{ArrowDown}')
expect(document.activeElement).toHaveAccessibleName('Option 5')
expect(document.activeElement).toHaveAccessibleDescription('Unavailable due to an outage')
const inactiveOption = page.getByRole('option', {name: 'Option 5', exact: true})
await expect.element(inactiveOption).toHaveFocus()
await expect.element(inactiveOption).toHaveAccessibleName('Option 5')
await expect.element(inactiveOption).toHaveAccessibleDescription('Unavailable due to an outage')

await userEvent.keyboard('{ArrowUp}')
expect(document.activeElement).toHaveTextContent('Option 4')
await expect.element(page.getByRole('option', {name: 'Option 4', exact: true})).toHaveFocus()
})

it('divider should support a custom `className`', () => {
Expand Down Expand Up @@ -139,10 +141,10 @@ describe('ActionList', () => {
)

await userEvent.tab() // tab into the story, this should focus on the first button
expect(document.activeElement).toHaveTextContent('Option 1')
await expect.element(page.getByRole('option', {name: 'Option 1', exact: true})).toHaveFocus()

await userEvent.keyboard('{ArrowDown}')
expect(document.activeElement).toHaveTextContent('Option 1')
await expect.element(page.getByRole('option', {name: 'Option 1', exact: true})).toHaveFocus()

expect(container.querySelector('li[aria-disabled="true"]')?.nextElementSibling).toHaveTextContent('Option 4')
expect(container.querySelector('li[aria-disabled="true"]')?.nextElementSibling).toHaveAttribute('tabindex', '0')
Expand Down Expand Up @@ -540,7 +542,7 @@ describe('ActionList with role="tree"', () => {
</ActionList>,
)

const item = document.querySelector('[role="treeitem"]')!
const item = page.getByRole('treeitem')
await userEvent.click(item)
expect(onSelect).toHaveBeenCalledTimes(1)
})
Expand All @@ -555,8 +557,10 @@ describe('ActionList with role="tree"', () => {
</ActionList>,
)

const item = document.querySelector('[role="treeitem"]')!
;(item as HTMLElement).focus()
const item = page.getByRole('treeitem')
act(() => {
item.element().focus()
})
await userEvent.keyboard('{Enter}')
expect(onSelect).toHaveBeenCalledTimes(1)
})
Expand All @@ -571,8 +575,8 @@ describe('ActionList with role="tree"', () => {
</ActionList>,
)

const item = document.querySelector('[role="treeitem"]')!
await userEvent.click(item)
const item = page.getByRole('treeitem')
fireEvent.click(item.element())
expect(onSelect).not.toHaveBeenCalled()
})

Expand Down
41 changes: 21 additions & 20 deletions packages/react/src/ActionList/Description.test.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import {page} from 'vitest/browser'
import {expect, it, describe} from 'vitest'
import {render as HTMLRender} from '@testing-library/react'
import {ActionList} from '.'
Expand All @@ -6,52 +7,52 @@ import classes from './ActionList.module.css'

describe('ActionList.Description', () => {
implementsClassName(ActionList.Description, classes.Description)
it('should render the description as inline without truncation by default', () => {
const {getByText} = HTMLRender(
it('should render the description as inline without truncation by default', async () => {
HTMLRender(
<ActionList>
<ActionList.Item>
Item 1<ActionList.Description>Item 1 description</ActionList.Description>
</ActionList.Item>
</ActionList>,
)

const description = getByText('Item 1 description')
expect(description.tagName).toBe('SPAN')
expect(description).toHaveStyle('flex-basis: auto')
expect(description).not.toHaveStyle('overflow: ellipsis')
expect(description).not.toHaveStyle('white-space: nowrap')
const description = page.getByText('Item 1 description', {exact: true})
expect(description.element().tagName).toBe('SPAN')
await expect.element(description).toHaveStyle('flex-basis: auto')
await expect.element(description).not.toHaveStyle('overflow: ellipsis')
await expect.element(description).not.toHaveStyle('white-space: nowrap')
})
it('should render the description as `Truncate` when truncate is true', () => {
const {getByText} = HTMLRender(
it('should render the description as `Truncate` when truncate is true', async () => {
HTMLRender(
<ActionList>
<ActionList.Item>
Item 1<ActionList.Description truncate>Item 1 description</ActionList.Description>
</ActionList.Item>
</ActionList>,
)

const description = getByText('Item 1 description')
expect(description.tagName).toBe('DIV')
const description = page.getByText('Item 1 description', {exact: true})
expect(description.element().tagName).toBe('DIV')
// For button-semantic items, the native title is suppressed in favor of
// a keyboard-accessible Tooltip rendered by the parent Item.
expect(description).toHaveAttribute('title', '')
expect(description).toHaveStyle('flex-basis: auto')
expect(description).toHaveStyle('text-overflow: ellipsis')
expect(description).toHaveStyle('overflow: hidden')
expect(description).toHaveStyle('white-space: nowrap')
await expect.element(description).toHaveAttribute('title', '')
await expect.element(description).toHaveStyle('flex-basis: auto')
await expect.element(description).toHaveStyle('text-overflow: ellipsis')
await expect.element(description).toHaveStyle('overflow: hidden')
await expect.element(description).toHaveStyle('white-space: nowrap')
})
it('should render the description in a new line when variant is block', () => {
const {getByText} = HTMLRender(
HTMLRender(
<ActionList>
<ActionList.Item>
Item 1<ActionList.Description variant="block">Item 1 description</ActionList.Description>
</ActionList.Item>
</ActionList>,
)

const description = getByText('Item 1 description')
expect(description.tagName).toBe('SPAN')
expect(description.parentElement?.parentElement).toHaveAttribute(
const description = page.getByText('Item 1 description', {exact: true})
expect(description.element().tagName).toBe('SPAN')
expect(description.element().parentElement?.parentElement).toHaveAttribute(
'data-component',
'ActionList.Item--DividerContainer',
)
Expand Down
61 changes: 31 additions & 30 deletions packages/react/src/ActionList/Group.test.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import {page} from 'vitest/browser'
import {describe, it, expect} from 'vitest'
import {render as HTMLRender} from '@testing-library/react'
import {PlusIcon} from '@primer/octicons-react'
Expand Down Expand Up @@ -45,17 +46,17 @@ describe('ActionList.Group', () => {
})

it('should render the ActionList.GroupHeading component as a heading with the given heading level', async () => {
const container = HTMLRender(
HTMLRender(
<ActionList>
<ActionList.Heading as="h1">Heading</ActionList.Heading>
<ActionList.Group>
<ActionList.GroupHeading as="h2">Group Heading</ActionList.GroupHeading>
</ActionList.Group>
</ActionList>,
)
const heading = container.getByRole('heading', {level: 2})
expect(heading).toBeInTheDocument()
expect(heading).toHaveTextContent('Group Heading')
const heading = page.getByRole('heading', {level: 2})
await expect.element(heading).toBeInTheDocument()
await expect.element(heading).toHaveTextContent('Group Heading')
})
it('should throw an error if ActionList.GroupHeading is used without an `as` prop when no role is specified (for list role)', async () => {
withExpectedConsoleError(() => {
Expand All @@ -75,34 +76,34 @@ describe('ActionList.Group', () => {
})
})
it('should render the ActionList.GroupHeading component as a span (not a heading tag) when role is specified as listbox', async () => {
const container = HTMLRender(
HTMLRender(
<ActionList role="listbox">
<ActionList.Heading as="h1">Heading</ActionList.Heading>
<ActionList.Group>
<ActionList.GroupHeading>Group Heading</ActionList.GroupHeading>
</ActionList.Group>
</ActionList>,
)
const label = container.getByText('Group Heading')
expect(label).toBeInTheDocument()
expect(label.tagName).toEqual('SPAN')
const label = page.getByText('Group Heading', {exact: true})
await expect.element(label).toBeInTheDocument()
expect(label.element().tagName).toEqual('SPAN')
})
it('should render the ActionList.GroupHeading component as a span with role="presentation" and aria-hidden="true" when role is specified as listbox', async () => {
const container = HTMLRender(
HTMLRender(
<ActionList role="listbox">
<ActionList.Heading as="h1">Heading</ActionList.Heading>
<ActionList.Group>
<ActionList.GroupHeading>Group Heading</ActionList.GroupHeading>
</ActionList.Group>
</ActionList>,
)
const label = container.getByText('Group Heading')
const wrapper = label.parentElement
const label = page.getByText('Group Heading', {exact: true})
const wrapper = label.element().parentElement
expect(wrapper).toHaveAttribute('role', 'presentation')
expect(wrapper).toHaveAttribute('aria-hidden', 'true')
})
it('should label the list with the group heading id', async () => {
const {container, getByText} = HTMLRender(
const {container} = HTMLRender(
<ActionList>
<ActionList.Heading as="h1">Heading</ActionList.Heading>
<ActionList.Group data-test-id="ActionList.Group">
Expand All @@ -112,11 +113,11 @@ describe('ActionList.Group', () => {
</ActionList>,
)
const list = container.querySelector(`li[data-test-id='ActionList.Group'] > ul`)
const heading = getByText('Group Heading')
expect(list).toHaveAttribute('aria-labelledby', heading.id)
const heading = page.getByText('Group Heading', {exact: true})
expect(list).toHaveAttribute('aria-labelledby', heading.element().id)
})
it('should NOT label the list with the group heading id when role is specified', async () => {
const {container, getByText} = HTMLRender(
const {container} = HTMLRender(
<ActionList role="listbox">
<ActionList.Heading as="h1">Heading</ActionList.Heading>
<ActionList.Group data-test-id="ActionList.Group">
Expand All @@ -126,8 +127,8 @@ describe('ActionList.Group', () => {
</ActionList>,
)
const list = container.querySelector(`li[data-test-id='ActionList.Group'] > ul`)
const heading = getByText('Group Heading')
expect(list).not.toHaveAttribute('aria-labelledby', heading.id)
const heading = page.getByText('Group Heading', {exact: true})
expect(list).not.toHaveAttribute('aria-labelledby', heading.element().id)
})

it('should label the list with aria-label if it is specified', async () => {
Expand All @@ -144,8 +145,8 @@ describe('ActionList.Group', () => {
})

describe('GroupHeading.TrailingAction (behind feature flag)', () => {
it('renders GroupHeading.TrailingAction as a sibling of the heading element when the feature flag is enabled', () => {
const {getByRole} = HTMLRender(
it('renders GroupHeading.TrailingAction as a sibling of the heading element when the feature flag is enabled', async () => {
HTMLRender(
<FeatureFlags flags={{primer_react_action_list_group_heading_trailing_action: true}}>
<ActionList>
<ActionList.Heading as="h1">Heading</ActionList.Heading>
Expand All @@ -160,20 +161,20 @@ describe('ActionList.Group', () => {
</FeatureFlags>,
)

const heading = getByRole('heading', {level: 2})
const button = getByRole('button', {name: 'New field'})
const heading = page.getByRole('heading', {level: 2})
const button = page.getByRole('button', {name: 'New field', exact: true})

// The button must NOT be inside the heading element
expect(heading).not.toContainElement(button)
await expect.element(heading).not.toContainElement(button.element())
// The heading text should not include the button's accessible name
expect(heading).toHaveTextContent('Group Heading')
expect(heading.textContent).not.toContain('New field')
await expect.element(heading).toHaveTextContent('Group Heading')
expect(heading.element().textContent).not.toContain('New field')
// The button should be inside the same wrapper as the heading
expect(heading.parentElement).toContainElement(button)
expect(heading.element().parentElement).toContainElement(button.element())
})

it('passes GroupHeading.TrailingAction through as a child of the heading when the feature flag is disabled', () => {
const {getByRole} = HTMLRender(
it('passes GroupHeading.TrailingAction through as a child of the heading when the feature flag is disabled', async () => {
HTMLRender(
<ActionList>
<ActionList.Heading as="h1">Heading</ActionList.Heading>
<ActionList.Group>
Expand All @@ -186,12 +187,12 @@ describe('ActionList.Group', () => {
</ActionList>,
)

const heading = getByRole('heading', {level: 2})
const button = getByRole('button', {name: 'New field'})
const heading = page.getByRole('heading', {level: 2})
const button = page.getByRole('button', {name: 'New field', exact: true})

// Without the flag, the slot is not consumed: the button passes
// through and still renders inside the heading element.
expect(heading).toContainElement(button)
await expect.element(heading).toContainElement(button.element())
})

it('throws when GroupHeading.TrailingAction is used inside an ActionMenu (menu role) and the feature flag is enabled', () => {
Expand Down
Loading
Loading