Skip to content
Merged
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
7 changes: 7 additions & 0 deletions .changeset/native-rest-forwarding.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@astryxdesign/core': patch
---

[fix] Forward unhandled pass-through attributes (`data-testid`, `aria-*`, `id`, etc.) to the primary rendered element of Switch, Pagination, RadioListItem, SideNavSection, TableHeader/TableBody/TableFooter, and TopNavMegaMenuFeaturedCard. These components previously dropped attributes not explicitly consumed, so test hooks and accessibility attributes silently disappeared.

@cixzhang
19 changes: 19 additions & 0 deletions packages/core/src/Pagination/Pagination.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -903,4 +903,23 @@ describe('Pagination', () => {
).not.toBeInTheDocument();
});
});

describe('rest forwarding', () => {
it('forwards data-testid, id, and aria-* to the root nav', () => {
const {container} = render(
<Pagination
page={1}
onChange={() => {}}
totalPages={3}
data-testid="pager"
id="pager-1"
aria-describedby="hint"
/>,
);
const nav = container.querySelector('nav')!;
expect(nav).toHaveAttribute('data-testid', 'pager');
expect(nav).toHaveAttribute('id', 'pager-1');
expect(nav).toHaveAttribute('aria-describedby', 'hint');
});
});
});
8 changes: 5 additions & 3 deletions packages/core/src/Pagination/Pagination.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,7 @@ export function Pagination({
className,
style,
ref,
...rest
}: PaginationProps) {
const [, startTransition] = useTransition();

Expand Down Expand Up @@ -610,14 +611,15 @@ export function Pagination({
return (
<nav
ref={ref}
aria-label={label}
data-testid={testId}
{...mergeProps(
themeProps('pagination', {variant, size}),
stylex.props(styles.root, xstyle),
className,
style,
)}>
)}
{...rest}
aria-label={label}
data-testid={testId}>
{pageSizeOptions != null && pageSizeOptions.length > 0 && (
<div {...stylex.props(styles.pageSizeSelector)}>
<div {...stylex.props(styles.pageSizeSelectorControl)}>
Expand Down
19 changes: 19 additions & 0 deletions packages/core/src/RadioList/RadioList.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -622,4 +622,23 @@ describe('RadioList', () => {
expect(input.getAttribute('name')).toBeTruthy();
});
});

describe('RadioListItem rest forwarding', () => {
it('forwards data-testid, id, and aria-* to the item root element', () => {
render(
<RadioList label="Preference" value="" onChange={() => {}}>
<RadioListItem
label="Option A"
value="a"
data-testid="item-a"
id="item-a-id"
aria-label="First option"
/>
</RadioList>,
);
const item = screen.getByTestId('item-a');
expect(item).toHaveAttribute('id', 'item-a-id');
expect(item).toHaveAttribute('aria-label', 'First option');
});
});
});
13 changes: 9 additions & 4 deletions packages/core/src/RadioList/RadioListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,10 @@ export function RadioListItem({
isDisabled: isItemDisabled = false,
startContent,
endContent,
'data-testid': dataTestId,
xstyle,
className,
style,
...rest
}: RadioListItemProps) {
const context = use(RadioListContext);
if (!context) {
Expand Down Expand Up @@ -313,11 +316,13 @@ export function RadioListItem({
return (
<div
ref={ref}
data-testid={dataTestId}
{...mergeProps(
themeProps('radio-list-item'),
stylex.props(styles.container, !isDisabled && radioScope),
)}>
stylex.props(styles.container, !isDisabled && radioScope, xstyle),
className,
style,
)}
{...rest}>
<Item
startContent={mediaContent}
label={
Expand Down
11 changes: 11 additions & 0 deletions packages/core/src/SideNav/SideNav.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -754,6 +754,17 @@ describe('SideNavSection', () => {
const group = screen.getByRole('group');
expect(group.style.marginTop).toBe('16px');
});

it('forwards arbitrary pass-through attributes (id, aria-*) to root element', () => {
render(
<SideNavSection title="Main" id="section-1" aria-describedby="hint">
<SideNavItem label="Dashboard" />
</SideNavSection>,
);
const group = screen.getByRole('group');
expect(group).toHaveAttribute('id', 'section-1');
expect(group).toHaveAttribute('aria-describedby', 'hint');
});
});

// =============================================================================
Expand Down
10 changes: 6 additions & 4 deletions packages/core/src/SideNav/SideNavSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ export function SideNavSection({
className,
style,
'data-testid': testId,
...rest
}: SideNavSectionProps) {
const {isCollapsed} = useSideNavCollapse();
const id = useId();
Expand Down Expand Up @@ -183,15 +184,16 @@ export function SideNavSection({
return (
<div
ref={ref}
role="group"
aria-labelledby={titleId}
data-testid={testId}
{...mergeProps(
themeProps('side-nav-section'),
stylex.props(styles.root, xstyle),
className,
style,
)}>
)}
{...rest}
role="group"
aria-labelledby={titleId}
data-testid={testId}>
<div
{...mergeProps(stylex.props(styles.header), {
style: shouldHideHeader ? visuallyHiddenStyle : undefined,
Expand Down
19 changes: 19 additions & 0 deletions packages/core/src/Switch/Switch.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -508,4 +508,23 @@ describe('Switch', () => {
expect([...data.keys()]).toEqual([]);
});
});

describe('rest forwarding', () => {
it('forwards data-testid, id, and aria-* to the root element', () => {
const {container} = render(
<Switch
label="Notifications"
value={false}
onChange={() => {}}
data-testid="my-switch"
id="switch-1"
aria-label="Toggle notifications"
/>,
);
const root = container.querySelector('[data-testid="my-switch"]');
expect(root).not.toBeNull();
expect(root).toHaveAttribute('id', 'switch-1');
expect(root).toHaveAttribute('aria-label', 'Toggle notifications');
});
});
});
4 changes: 3 additions & 1 deletion packages/core/src/Switch/Switch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,7 @@ export function Switch({
className,
style,
ref,
...rest
}: SwitchProps) {
const id = useId();
const descriptionID = useId();
Expand Down Expand Up @@ -492,7 +493,8 @@ export function Switch({
stylex.props(width != null && dynamicWidthStyles.width(width), xstyle),
className,
style,
)}>
)}
{...rest}>
<div
ref={el => {
// Interaction (hover/focus) listeners for the disabled-message
Expand Down
36 changes: 36 additions & 0 deletions packages/core/src/Table/Table.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ import {BaseTable} from './BaseTable';
import {Table} from './Table';
import {TableRow} from './TableRow';
import {TableCell} from './TableCell';
import {TableHeader} from './TableHeader';
import {TableBody} from './TableBody';
import {TableFooter} from './TableFooter';
import {
proportional,
pixel,
Expand Down Expand Up @@ -1283,4 +1286,37 @@ describe('emptyState', () => {
expect(screen.getByText('Name')).toBeInTheDocument();
expect(screen.getByText('Age')).toBeInTheDocument();
});

describe('table section rest forwarding', () => {
it('forwards data-testid and id to the tbody, thead, and tfoot', () => {
const {container} = render(
<table>
<TableHeader data-testid="thead" id="head-1">
<tr>
<th>H</th>
</tr>
</TableHeader>
<TableBody data-testid="tbody" id="body-1">
<tr>
<td>B</td>
</tr>
</TableBody>
<TableFooter data-testid="tfoot" id="foot-1">
<tr>
<td>F</td>
</tr>
</TableFooter>
</table>,
);
const thead = container.querySelector('thead')!;
const tbody = container.querySelector('tbody')!;
const tfoot = container.querySelector('tfoot')!;
expect(thead).toHaveAttribute('data-testid', 'thead');
expect(thead).toHaveAttribute('id', 'head-1');
expect(tbody).toHaveAttribute('data-testid', 'tbody');
expect(tbody).toHaveAttribute('id', 'body-1');
expect(tfoot).toHaveAttribute('data-testid', 'tfoot');
expect(tfoot).toHaveAttribute('id', 'foot-1');
});
});
});
17 changes: 15 additions & 2 deletions packages/core/src/Table/TableBody.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,24 @@ export interface TableBodyProps extends BaseProps<HTMLTableSectionElement> {
children: ReactNode;
}

export function TableBody({ref, children, xstyle}: TableBodyProps) {
export function TableBody({
ref,
children,
xstyle,
className,
style,
...rest
}: TableBodyProps) {
return (
<tbody
ref={ref}
{...mergeProps(themeProps('table-body'), stylex.props(xstyle))}>
{...mergeProps(
themeProps('table-body'),
stylex.props(xstyle),
className,
style,
)}
{...rest}>
{children}
</tbody>
);
Expand Down
17 changes: 15 additions & 2 deletions packages/core/src/Table/TableFooter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,24 @@ export interface TableFooterProps extends BaseProps<HTMLTableSectionElement> {
children: ReactNode;
}

export function TableFooter({ref, children, xstyle}: TableFooterProps) {
export function TableFooter({
ref,
children,
xstyle,
className,
style,
...rest
}: TableFooterProps) {
return (
<tfoot
ref={ref}
{...mergeProps(themeProps('table-footer'), stylex.props(xstyle))}>
{...mergeProps(
themeProps('table-footer'),
stylex.props(xstyle),
className,
style,
)}
{...rest}>
{children}
</tfoot>
);
Expand Down
17 changes: 15 additions & 2 deletions packages/core/src/Table/TableHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,24 @@ export interface TableHeaderProps extends BaseProps<HTMLTableSectionElement> {
children: ReactNode;
}

export function TableHeader({ref, children, xstyle}: TableHeaderProps) {
export function TableHeader({
ref,
children,
xstyle,
className,
style,
...rest
}: TableHeaderProps) {
return (
<thead
ref={ref}
{...mergeProps(themeProps('table-header'), stylex.props(xstyle))}>
{...mergeProps(
themeProps('table-header'),
stylex.props(xstyle),
className,
style,
)}
{...rest}>
{children}
</thead>
);
Expand Down
18 changes: 18 additions & 0 deletions packages/core/src/TopNav/TopNav.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {TopNav} from './TopNav';
import {TopNavHeading} from './TopNavHeading';
import {NavIcon} from '../NavIcon';
import {TopNavItem} from './TopNavItem';
import {TopNavMegaMenuFeaturedCard} from './TopNavMegaMenuFeaturedCard';
import {LinkProvider} from '../Link/LinkProvider';

function CustomLink({
Expand Down Expand Up @@ -398,4 +399,21 @@ describe('TopNavItem', () => {
expect(link).toHaveAttribute('data-custom-link');
expect(link).not.toHaveAttribute('data-another-link');
});

describe('TopNavMegaMenuFeaturedCard rest forwarding', () => {
it('forwards data-testid, id, and aria-* to the root element', () => {
render(
<TopNavMegaMenuFeaturedCard
title="What's new"
description="Details"
data-testid="featured-card"
id="card-1"
aria-label="Featured"
/>,
);
const card = screen.getByTestId('featured-card');
expect(card).toHaveAttribute('id', 'card-1');
expect(card).toHaveAttribute('aria-label', 'Featured');
});
});
});
11 changes: 9 additions & 2 deletions packages/core/src/TopNav/TopNavMegaMenuFeaturedCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -132,15 +132,22 @@ export function TopNavMegaMenuFeaturedCard({
linkLabel,
linkHref,
children,
xstyle,
className,
style,
...rest
}: TopNavMegaMenuFeaturedCardProps) {
const LinkComponent = useLinkComponent();
return (
<div
ref={ref}
{...mergeProps(
themeProps('top-nav-mega-menu-featured-card'),
stylex.props(styles.root),
)}>
stylex.props(styles.root, xstyle),
className,
style,
)}
{...rest}>
{image && (
<img src={image} alt={imageAlt ?? ''} {...stylex.props(styles.image)} />
)}
Expand Down
Loading