Skip to content

Commit d48f4fa

Browse files
committed
Merge branch 'release-2.18.2' into release
2 parents 42e1a78 + 3a48f96 commit d48f4fa

File tree

79 files changed

+1291
-784
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

79 files changed

+1291
-784
lines changed

.eslintrc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,9 @@
136136
"no-unused-vars": "off",
137137
"import/no-default-export": "warn",
138138
"no-underscore-dangle": "warn",
139+
"react/require-default-props": "off",
140+
"no-shadow": "off",
141+
"@typescript-eslint/no-shadow": "error"
139142
}
140143
},
141144
{

client/common/Button.stories.jsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React from 'react';
22
import { action } from '@storybook/addon-actions';
33

4-
import Button from './Button';
4+
import { Button, ButtonDisplays, ButtonKinds, ButtonTypes } from './Button';
55
import { GithubIcon, DropdownArrowIcon, PlusIcon } from './icons';
66

77
export default {
@@ -15,13 +15,13 @@ export default {
1515
};
1616

1717
export const AllFeatures = (args) => (
18-
<Button disabled={args.disabled} type="submit" label={args.label}>
18+
<Button disabled={args.disabled} type={ButtonTypes.SUBMIT} label={args.label}>
1919
{args.children}
2020
</Button>
2121
);
2222

2323
export const SubmitButton = () => (
24-
<Button type="submit" label="submit">
24+
<Button type={ButtonTypes.SUBMIT} label="submit">
2525
This is a submit button
2626
</Button>
2727
);
@@ -59,7 +59,7 @@ export const ButtonWithIconAfter = () => (
5959
);
6060

6161
export const InlineButtonWithIconAfter = () => (
62-
<Button iconAfter={<DropdownArrowIcon />} display={Button.displays.inline}>
62+
<Button iconAfter={<DropdownArrowIcon />} display={ButtonDisplays.INLINE}>
6363
File name
6464
</Button>
6565
);
@@ -68,6 +68,6 @@ export const InlineIconOnlyButton = () => (
6868
<Button
6969
aria-label="Add to collection"
7070
iconBefore={<PlusIcon />}
71-
display={Button.displays.inline}
71+
display={ButtonDisplays.INLINE}
7272
/>
7373
);

client/common/Button.test.tsx

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import React from 'react';
2+
import { render, screen, fireEvent } from '../test-utils';
3+
import { Button } from './Button';
4+
5+
const MockIcon = (props: React.SVGProps<SVGSVGElement>) => (
6+
<svg data-testid="mock-icon" {...props} />
7+
);
8+
9+
describe('Button', () => {
10+
// Tag
11+
it('renders as an anchor when href is provided', () => {
12+
render(<Button href="https://example.com">Link</Button>);
13+
const anchor = screen.getByRole('link');
14+
expect(anchor.tagName.toLowerCase()).toBe('a');
15+
expect(anchor).toHaveAttribute('href', 'https://example.com');
16+
});
17+
18+
it('renders as a React Router <Link> when `to` is provided', () => {
19+
render(<Button to="/dashboard">Go</Button>);
20+
const link = screen.getByRole('link');
21+
expect(link.tagName.toLowerCase()).toBe('a'); // Link renders as <a>
22+
expect(link).toHaveAttribute('href', '/dashboard');
23+
});
24+
25+
it('renders as a <button> with a type of "button" by default', () => {
26+
render(<Button>Click Me</Button>);
27+
const el = screen.getByRole('button');
28+
expect(el.tagName.toLowerCase()).toBe('button');
29+
expect(el).toHaveAttribute('type', 'button');
30+
});
31+
32+
// Children & Icons
33+
it('renders children', () => {
34+
render(<Button>Click Me</Button>);
35+
expect(screen.getByText('Click Me')).toBeInTheDocument();
36+
});
37+
38+
it('renders an iconBefore and button text', () => {
39+
render(
40+
<Button iconBefore={<MockIcon aria-label="iconbefore" />}>
41+
This has a before icon
42+
</Button>
43+
);
44+
expect(screen.getByLabelText('iconbefore')).toBeInTheDocument();
45+
expect(screen.getByRole('button')).toHaveTextContent(
46+
'This has a before icon'
47+
);
48+
});
49+
50+
it('renders with iconAfter', () => {
51+
render(
52+
<Button iconAfter={<MockIcon aria-label="iconafter" />}>
53+
This has an after icon
54+
</Button>
55+
);
56+
expect(screen.getByLabelText('iconafter')).toBeInTheDocument();
57+
expect(screen.getByRole('button')).toHaveTextContent(
58+
'This has an after icon'
59+
);
60+
});
61+
62+
it('renders only the icon if iconOnly', () => {
63+
render(
64+
<Button iconAfter={<MockIcon aria-label="iconafter" />} iconOnly>
65+
This has an after icon
66+
</Button>
67+
);
68+
expect(screen.getByLabelText('iconafter')).toBeInTheDocument();
69+
expect(screen.getByRole('button')).not.toHaveTextContent(
70+
'This has an after icon'
71+
);
72+
});
73+
74+
// HTML attributes
75+
it('calls onClick handler when clicked', () => {
76+
const handleClick = jest.fn();
77+
render(<Button onClick={handleClick}>Click</Button>);
78+
fireEvent.click(screen.getByText('Click'));
79+
expect(handleClick).toHaveBeenCalledTimes(1);
80+
});
81+
82+
it('renders disabled state', () => {
83+
render(<Button disabled>Disabled</Button>);
84+
expect(screen.getByRole('button')).toBeDisabled();
85+
});
86+
87+
it('uses aria-label when provided', () => {
88+
render(<Button aria-label="Upload" iconOnly />);
89+
expect(screen.getByLabelText('Upload')).toBeInTheDocument();
90+
});
91+
});

0 commit comments

Comments
 (0)