@evo-web/react 0.0.1 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/evo-button/button-cell.d.ts +8 -0
- package/dist/evo-button/button-cell.d.ts.map +1 -0
- package/dist/evo-button/button-cell.js +15 -0
- package/dist/evo-button/button-cell.js.map +1 -0
- package/dist/evo-button/button.d.ts +6 -0
- package/dist/evo-button/button.d.ts.map +1 -0
- package/dist/evo-button/button.js +50 -0
- package/dist/evo-button/button.js.map +1 -0
- package/dist/evo-button/index.d.ts +4 -0
- package/dist/evo-button/index.d.ts.map +1 -0
- package/{src/evo-button/types.ts → dist/evo-button/types.d.ts} +17 -22
- package/dist/evo-button/types.d.ts.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/package.json +4 -1
- package/.prettierignore +0 -3
- package/.storybook/main.js +0 -32
- package/.storybook/manager-head.html +0 -6
- package/.storybook/manager.ts +0 -4
- package/.storybook/preview.tsx +0 -50
- package/.storybook/theme.ts +0 -17
- package/.storybook/vite.config.js +0 -3
- package/CHANGELOG.md +0 -11
- package/eslint.config.js +0 -53
- package/src/evo-button/button-cell.tsx +0 -21
- package/src/evo-button/button.stories.tsx +0 -113
- package/src/evo-button/button.tsx +0 -138
- package/src/evo-button/index.ts +0 -10
- package/src/evo-button/test/__snapshots__/test.server.tsx.snap +0 -83
- package/src/evo-button/test/test.browser.tsx +0 -298
- package/src/evo-button/test/test.server.tsx +0 -245
- package/src/index.ts +0 -9
- package/test.setup.ts +0 -7
- package/tsconfig.json +0 -21
- package/tsconfig.prod.json +0 -5
- package/vite.config.js +0 -88
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
import type { KeyboardEvent } from "react";
|
|
2
|
-
import React from "react";
|
|
3
|
-
import classNames from "classnames";
|
|
4
|
-
import type {
|
|
5
|
-
AnchorButtonProps,
|
|
6
|
-
NativeButtonProps,
|
|
7
|
-
Priority,
|
|
8
|
-
Size,
|
|
9
|
-
Split,
|
|
10
|
-
} from "./types";
|
|
11
|
-
import "@ebay/skin/button";
|
|
12
|
-
|
|
13
|
-
export function EvoButton(props: AnchorButtonProps): React.JSX.Element;
|
|
14
|
-
export function EvoButton(props: NativeButtonProps): React.JSX.Element;
|
|
15
|
-
export function EvoButton(
|
|
16
|
-
props: AnchorButtonProps | NativeButtonProps,
|
|
17
|
-
): React.JSX.Element {
|
|
18
|
-
const {
|
|
19
|
-
priority = "secondary",
|
|
20
|
-
variant = "standard",
|
|
21
|
-
size,
|
|
22
|
-
bodyState,
|
|
23
|
-
split,
|
|
24
|
-
transparent = false,
|
|
25
|
-
fluid = false,
|
|
26
|
-
disabled,
|
|
27
|
-
partiallyDisabled,
|
|
28
|
-
children,
|
|
29
|
-
onKeyDown,
|
|
30
|
-
onEscape,
|
|
31
|
-
truncate = false,
|
|
32
|
-
href,
|
|
33
|
-
className: extraClasses,
|
|
34
|
-
borderless,
|
|
35
|
-
fixedHeight,
|
|
36
|
-
...rest
|
|
37
|
-
} = props;
|
|
38
|
-
const classPrefix = href ? "fake-btn" : "btn";
|
|
39
|
-
const priorityStyles: { [key in Priority]: string } = {
|
|
40
|
-
primary: `${classPrefix}--primary`,
|
|
41
|
-
secondary: `${classPrefix}--secondary`,
|
|
42
|
-
tertiary: `${classPrefix}--tertiary`,
|
|
43
|
-
none: "",
|
|
44
|
-
};
|
|
45
|
-
const sizeStyles: { [key in Size]: string } = {
|
|
46
|
-
large: `${classPrefix}--large`,
|
|
47
|
-
small: `${classPrefix}--small`,
|
|
48
|
-
};
|
|
49
|
-
const splitStyles: { [key in Split]: string } = {
|
|
50
|
-
start: `${classPrefix}--split-start`,
|
|
51
|
-
end: `${classPrefix}--split-end`,
|
|
52
|
-
};
|
|
53
|
-
const isDestructive = variant === "destructive";
|
|
54
|
-
const isForm = variant === "form";
|
|
55
|
-
const className = classNames(
|
|
56
|
-
classPrefix,
|
|
57
|
-
extraClasses,
|
|
58
|
-
priorityStyles[isForm || borderless ? "none" : priority],
|
|
59
|
-
size && sizeStyles[size],
|
|
60
|
-
split && splitStyles[split],
|
|
61
|
-
isDestructive && `${classPrefix}--destructive`,
|
|
62
|
-
isForm && `${classPrefix}--form`,
|
|
63
|
-
transparent && `${classPrefix}--transparent`,
|
|
64
|
-
fluid && `${classPrefix}--fluid`,
|
|
65
|
-
truncate && `${classPrefix}--truncated`,
|
|
66
|
-
borderless && `${classPrefix}--borderless`,
|
|
67
|
-
fixedHeight &&
|
|
68
|
-
(size && sizeStyles[size]
|
|
69
|
-
? `${sizeStyles[size]}-fixed-height`
|
|
70
|
-
: `${classPrefix}--fixed-height`),
|
|
71
|
-
);
|
|
72
|
-
|
|
73
|
-
const bodyContent = (() => {
|
|
74
|
-
switch (bodyState) {
|
|
75
|
-
case "loading":
|
|
76
|
-
return (
|
|
77
|
-
<span className="btn__cell">
|
|
78
|
-
{/* TODO: Replace with <EvoProgressSpinner /> when available */}
|
|
79
|
-
<span>Loading...</span>
|
|
80
|
-
</span>
|
|
81
|
-
);
|
|
82
|
-
case "expand":
|
|
83
|
-
return (
|
|
84
|
-
<span className="btn__cell">
|
|
85
|
-
<span className="btn__text">{children}</span>
|
|
86
|
-
{/* TODO: Replace with <EvoIconChevronDown16 /> when available */}
|
|
87
|
-
<span>▼</span>
|
|
88
|
-
</span>
|
|
89
|
-
);
|
|
90
|
-
default:
|
|
91
|
-
return children;
|
|
92
|
-
}
|
|
93
|
-
})();
|
|
94
|
-
|
|
95
|
-
const ariaLive = bodyState === "loading" ? "polite" : undefined;
|
|
96
|
-
|
|
97
|
-
const keyDownHandler = (
|
|
98
|
-
event: KeyboardEvent<HTMLButtonElement | HTMLAnchorElement>,
|
|
99
|
-
) => {
|
|
100
|
-
onKeyDown?.(
|
|
101
|
-
event as KeyboardEvent<HTMLButtonElement> &
|
|
102
|
-
KeyboardEvent<HTMLAnchorElement>,
|
|
103
|
-
);
|
|
104
|
-
if (event.key === "Escape" && !disabled && onEscape) {
|
|
105
|
-
onEscape(
|
|
106
|
-
event as KeyboardEvent<HTMLButtonElement> &
|
|
107
|
-
KeyboardEvent<HTMLAnchorElement>,
|
|
108
|
-
);
|
|
109
|
-
}
|
|
110
|
-
};
|
|
111
|
-
|
|
112
|
-
if (href) {
|
|
113
|
-
return (
|
|
114
|
-
<a
|
|
115
|
-
{...(rest as React.ComponentProps<"a">)}
|
|
116
|
-
className={className}
|
|
117
|
-
href={disabled ? undefined : href}
|
|
118
|
-
onKeyDown={keyDownHandler}
|
|
119
|
-
aria-live={ariaLive}
|
|
120
|
-
>
|
|
121
|
-
{bodyContent}
|
|
122
|
-
</a>
|
|
123
|
-
);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
return (
|
|
127
|
-
<button
|
|
128
|
-
{...(rest as React.ComponentProps<"button">)}
|
|
129
|
-
disabled={disabled}
|
|
130
|
-
aria-disabled={partiallyDisabled ? "true" : undefined}
|
|
131
|
-
aria-live={ariaLive}
|
|
132
|
-
className={className}
|
|
133
|
-
onKeyDown={keyDownHandler}
|
|
134
|
-
>
|
|
135
|
-
{bodyContent}
|
|
136
|
-
</button>
|
|
137
|
-
);
|
|
138
|
-
}
|
package/src/evo-button/index.ts
DELETED
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
|
-
|
|
3
|
-
exports[`EvoButton SSR > should render button with bodyState=expand 1`] = `"<button class="btn btn--secondary"><span class="btn__cell"><span class="btn__text">Button</span><span>▼</span></span></button>"`;
|
|
4
|
-
|
|
5
|
-
exports[`EvoButton SSR > should render button with bodyState=loading 1`] = `"<button aria-live="polite" class="btn btn--secondary"><span class="btn__cell"><span>Loading...</span></span></button>"`;
|
|
6
|
-
|
|
7
|
-
exports[`EvoButton SSR > should render button with borderless=false 1`] = `"<button class="btn btn--secondary">Button</button>"`;
|
|
8
|
-
|
|
9
|
-
exports[`EvoButton SSR > should render button with borderless=true 1`] = `"<button class="btn btn--borderless">Button</button>"`;
|
|
10
|
-
|
|
11
|
-
exports[`EvoButton SSR > should render button with disabled=false 1`] = `"<button class="btn btn--secondary">Button</button>"`;
|
|
12
|
-
|
|
13
|
-
exports[`EvoButton SSR > should render button with disabled=true 1`] = `"<button disabled="" class="btn btn--secondary">Button</button>"`;
|
|
14
|
-
|
|
15
|
-
exports[`EvoButton SSR > should render button with fixedHeight=false 1`] = `"<button class="btn btn--secondary">Button</button>"`;
|
|
16
|
-
|
|
17
|
-
exports[`EvoButton SSR > should render button with fixedHeight=true 1`] = `"<button class="btn btn--secondary btn--fixed-height">Button</button>"`;
|
|
18
|
-
|
|
19
|
-
exports[`EvoButton SSR > should render button with fluid=false 1`] = `"<button class="btn btn--secondary">Button</button>"`;
|
|
20
|
-
|
|
21
|
-
exports[`EvoButton SSR > should render button with fluid=true 1`] = `"<button class="btn btn--secondary btn--fluid">Button</button>"`;
|
|
22
|
-
|
|
23
|
-
exports[`EvoButton SSR > should render button with partiallyDisabled=false 1`] = `"<button class="btn btn--secondary">Button</button>"`;
|
|
24
|
-
|
|
25
|
-
exports[`EvoButton SSR > should render button with partiallyDisabled=true 1`] = `"<button aria-disabled="true" class="btn btn--secondary">Button</button>"`;
|
|
26
|
-
|
|
27
|
-
exports[`EvoButton SSR > should render button with priority=none 1`] = `"<button class="btn">Button</button>"`;
|
|
28
|
-
|
|
29
|
-
exports[`EvoButton SSR > should render button with priority=primary 1`] = `"<button class="btn btn--primary">Button</button>"`;
|
|
30
|
-
|
|
31
|
-
exports[`EvoButton SSR > should render button with priority=secondary 1`] = `"<button class="btn btn--secondary">Button</button>"`;
|
|
32
|
-
|
|
33
|
-
exports[`EvoButton SSR > should render button with priority=tertiary 1`] = `"<button class="btn btn--tertiary">Button</button>"`;
|
|
34
|
-
|
|
35
|
-
exports[`EvoButton SSR > should render button with size=large 1`] = `"<button class="btn btn--secondary btn--large">Button</button>"`;
|
|
36
|
-
|
|
37
|
-
exports[`EvoButton SSR > should render button with size=small 1`] = `"<button class="btn btn--secondary btn--small">Button</button>"`;
|
|
38
|
-
|
|
39
|
-
exports[`EvoButton SSR > should render button with split=end 1`] = `"<button class="btn btn--primary btn--split-end">Button</button>"`;
|
|
40
|
-
|
|
41
|
-
exports[`EvoButton SSR > should render button with split=start 1`] = `"<button class="btn btn--primary btn--split-start">Button</button>"`;
|
|
42
|
-
|
|
43
|
-
exports[`EvoButton SSR > should render button with transparent=false 1`] = `"<button class="btn btn--secondary">Button</button>"`;
|
|
44
|
-
|
|
45
|
-
exports[`EvoButton SSR > should render button with transparent=true 1`] = `"<button class="btn btn--secondary btn--transparent">Button</button>"`;
|
|
46
|
-
|
|
47
|
-
exports[`EvoButton SSR > should render button with truncate=false 1`] = `"<button class="btn btn--secondary">Button</button>"`;
|
|
48
|
-
|
|
49
|
-
exports[`EvoButton SSR > should render button with truncate=true 1`] = `"<button class="btn btn--secondary btn--truncated">Button</button>"`;
|
|
50
|
-
|
|
51
|
-
exports[`EvoButton SSR > should render button with variant=destructive 1`] = `"<button class="btn btn--secondary btn--destructive">Button</button>"`;
|
|
52
|
-
|
|
53
|
-
exports[`EvoButton SSR > should render button with variant=form 1`] = `"<button class="btn btn--form">Button</button>"`;
|
|
54
|
-
|
|
55
|
-
exports[`EvoButton SSR > should render button with variant=standard 1`] = `"<button class="btn btn--secondary">Button</button>"`;
|
|
56
|
-
|
|
57
|
-
exports[`EvoButton SSR > should render combined: primary large fluid 1`] = `"<button class="btn btn--primary btn--large btn--fluid">Combined</button>"`;
|
|
58
|
-
|
|
59
|
-
exports[`EvoButton SSR > should render defaults 1`] = `"<button class="btn btn--secondary">Default Button</button>"`;
|
|
60
|
-
|
|
61
|
-
exports[`EvoButton SSR > should render destructive primary large 1`] = `"<button class="btn btn--primary btn--large btn--destructive">Delete</button>"`;
|
|
62
|
-
|
|
63
|
-
exports[`EvoButton SSR > should render disabled link without href 1`] = `"<a class="fake-btn fake-btn--secondary">Disabled Link</a>"`;
|
|
64
|
-
|
|
65
|
-
exports[`EvoButton SSR > should render fake version (anchor) 1`] = `"<a aria-label="fake button" class="fake-btn fake-btn--primary fake-btn--large" href="https://ebay.com">Link Button</a>"`;
|
|
66
|
-
|
|
67
|
-
exports[`EvoButton SSR > should render form variant with expand 1`] = `"<button class="btn btn--form"><span class="btn__cell"><span class="btn__text">Form Expand</span><span>▼</span></span></button>"`;
|
|
68
|
-
|
|
69
|
-
exports[`EvoButton SSR > should render large fixed-height button 1`] = `"<button class="btn btn--secondary btn--large btn--large-fixed-height">Large Fixed Height</button>"`;
|
|
70
|
-
|
|
71
|
-
exports[`EvoButton SSR > should render large truncated button 1`] = `"<button class="btn btn--secondary btn--large btn--truncated">Large Truncated</button>"`;
|
|
72
|
-
|
|
73
|
-
exports[`EvoButton SSR > should render with ButtonCell 1`] = `"<button class="btn btn--secondary"><span class="btn__cell" style="justify-content:space-between"><span>Left</span><span>Right</span></span></button>"`;
|
|
74
|
-
|
|
75
|
-
exports[`EvoButton SSR > should render with aria-label 1`] = `"<button aria-label="Submit form" class="btn btn--secondary">Submit</button>"`;
|
|
76
|
-
|
|
77
|
-
exports[`EvoButton SSR > should render with custom className 1`] = `"<button class="btn custom-class btn--secondary">Button</button>"`;
|
|
78
|
-
|
|
79
|
-
exports[`EvoButton SSR > should render with data attributes 1`] = `"<button data-testid="button" class="btn btn--secondary">Button</button>"`;
|
|
80
|
-
|
|
81
|
-
exports[`EvoButton SSR > should render with id override 1`] = `"<button id="test" class="btn btn--secondary">Button</button>"`;
|
|
82
|
-
|
|
83
|
-
exports[`EvoButton SSR > should render with type override 1`] = `"<button type="submit" class="btn btn--secondary">Submit</button>"`;
|
|
@@ -1,298 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
|
3
|
-
import { render } from "vitest-browser-react";
|
|
4
|
-
import { userEvent } from "vitest/browser";
|
|
5
|
-
import { EvoButton } from "../button";
|
|
6
|
-
import { EvoButtonCell } from "../button-cell";
|
|
7
|
-
|
|
8
|
-
describe("evo-button", () => {
|
|
9
|
-
let user: ReturnType<typeof userEvent.setup>;
|
|
10
|
-
|
|
11
|
-
beforeEach(() => {
|
|
12
|
-
user = userEvent.setup();
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
afterEach(() => {
|
|
16
|
-
user.cleanup();
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
describe("given button is enabled", () => {
|
|
20
|
-
it("emits click event when clicked", async () => {
|
|
21
|
-
const onClick = vi.fn();
|
|
22
|
-
const screen = await render(
|
|
23
|
-
<EvoButton onClick={onClick}>Click Me</EvoButton>,
|
|
24
|
-
);
|
|
25
|
-
|
|
26
|
-
await user.click(screen.getByRole("button"));
|
|
27
|
-
|
|
28
|
-
expect(onClick).toHaveBeenCalledTimes(1);
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
it("emits escape event when escape key is pressed", async () => {
|
|
32
|
-
const onEscape = vi.fn();
|
|
33
|
-
const screen = await render(
|
|
34
|
-
<EvoButton onEscape={onEscape}>Button</EvoButton>,
|
|
35
|
-
);
|
|
36
|
-
|
|
37
|
-
const button = screen.getByRole("button");
|
|
38
|
-
await user.click(button);
|
|
39
|
-
await user.keyboard("{Escape}");
|
|
40
|
-
|
|
41
|
-
expect(onEscape).toHaveBeenCalledTimes(1);
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it("emits both onKeyDown and onEscape on escape key", async () => {
|
|
45
|
-
const onKeyDown = vi.fn();
|
|
46
|
-
const onEscape = vi.fn();
|
|
47
|
-
const screen = await render(
|
|
48
|
-
<EvoButton onKeyDown={onKeyDown} onEscape={onEscape}>
|
|
49
|
-
Button
|
|
50
|
-
</EvoButton>,
|
|
51
|
-
);
|
|
52
|
-
|
|
53
|
-
const button = screen.getByRole("button");
|
|
54
|
-
await user.click(button);
|
|
55
|
-
await user.keyboard("{Escape}");
|
|
56
|
-
|
|
57
|
-
expect(onKeyDown).toHaveBeenCalledTimes(1);
|
|
58
|
-
expect(onEscape).toHaveBeenCalledTimes(1);
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
it("emits focus event when focused", async () => {
|
|
62
|
-
const onFocus = vi.fn();
|
|
63
|
-
const screen = await render(
|
|
64
|
-
<EvoButton onFocus={onFocus}>Button</EvoButton>,
|
|
65
|
-
);
|
|
66
|
-
|
|
67
|
-
await user.click(screen.getByRole("button"));
|
|
68
|
-
|
|
69
|
-
expect(onFocus).toHaveBeenCalledTimes(1);
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it("emits blur event when blurred", async () => {
|
|
73
|
-
const onBlur = vi.fn();
|
|
74
|
-
const screen = await render(
|
|
75
|
-
<EvoButton onBlur={onBlur}>Button</EvoButton>,
|
|
76
|
-
);
|
|
77
|
-
|
|
78
|
-
const button = screen.getByRole("button");
|
|
79
|
-
await user.click(button);
|
|
80
|
-
await user.tab();
|
|
81
|
-
|
|
82
|
-
expect(onBlur).toHaveBeenCalledTimes(1);
|
|
83
|
-
});
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
describe("given button is disabled", () => {
|
|
87
|
-
it("does not emit click event when clicked", async () => {
|
|
88
|
-
const onClick = vi.fn();
|
|
89
|
-
const screen = await render(
|
|
90
|
-
<EvoButton onClick={onClick} disabled>
|
|
91
|
-
Button
|
|
92
|
-
</EvoButton>,
|
|
93
|
-
);
|
|
94
|
-
|
|
95
|
-
const button = screen.getByRole("button");
|
|
96
|
-
await expect.element(button).toBeDisabled();
|
|
97
|
-
expect(onClick).not.toHaveBeenCalled();
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
it("does not emit escape event when escape key is pressed", async () => {
|
|
101
|
-
const onEscape = vi.fn();
|
|
102
|
-
const screen = await render(
|
|
103
|
-
<EvoButton onEscape={onEscape} disabled>
|
|
104
|
-
Button
|
|
105
|
-
</EvoButton>,
|
|
106
|
-
);
|
|
107
|
-
|
|
108
|
-
const button = screen.getByRole("button");
|
|
109
|
-
await expect.element(button).toBeDisabled();
|
|
110
|
-
expect(onEscape).not.toHaveBeenCalled();
|
|
111
|
-
});
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
describe("ref forwarding", () => {
|
|
115
|
-
it("forwards ref to button element", async () => {
|
|
116
|
-
const ref = React.createRef<HTMLButtonElement>();
|
|
117
|
-
await render(<EvoButton ref={ref}>Button</EvoButton>);
|
|
118
|
-
|
|
119
|
-
expect(ref.current).toBeInstanceOf(HTMLButtonElement);
|
|
120
|
-
expect(ref.current?.tagName).toBe("BUTTON");
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
it("forwards ref to anchor element when href is provided", async () => {
|
|
124
|
-
const ref = React.createRef<HTMLAnchorElement>();
|
|
125
|
-
await render(
|
|
126
|
-
<EvoButton href="https://ebay.com" ref={ref}>
|
|
127
|
-
Link
|
|
128
|
-
</EvoButton>,
|
|
129
|
-
);
|
|
130
|
-
|
|
131
|
-
expect(ref.current).toBeInstanceOf(HTMLAnchorElement);
|
|
132
|
-
expect(ref.current?.tagName).toBe("A");
|
|
133
|
-
});
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
describe("anchor element behavior", () => {
|
|
137
|
-
it("renders as link when href is provided", async () => {
|
|
138
|
-
const screen = await render(
|
|
139
|
-
<EvoButton
|
|
140
|
-
href="https://ebay.com"
|
|
141
|
-
priority="primary"
|
|
142
|
-
onClick={(e) => e.preventDefault()}
|
|
143
|
-
>
|
|
144
|
-
Link Button
|
|
145
|
-
</EvoButton>,
|
|
146
|
-
);
|
|
147
|
-
|
|
148
|
-
const link = screen.getByRole("link");
|
|
149
|
-
await expect.element(link).toHaveAttribute("href", "https://ebay.com");
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
it("does not render href when disabled", async () => {
|
|
153
|
-
const screen = await render(
|
|
154
|
-
<EvoButton href="https://ebay.com" disabled>
|
|
155
|
-
Disabled Link
|
|
156
|
-
</EvoButton>,
|
|
157
|
-
);
|
|
158
|
-
|
|
159
|
-
const link = screen.getByText("Disabled Link");
|
|
160
|
-
await expect.element(link).not.toHaveAttribute("href");
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
it("emits escape event on anchor element", async () => {
|
|
164
|
-
const onEscape = vi.fn();
|
|
165
|
-
const screen = await render(
|
|
166
|
-
<EvoButton
|
|
167
|
-
href="https://ebay.com"
|
|
168
|
-
onEscape={onEscape}
|
|
169
|
-
onClick={(e) => e.preventDefault()}
|
|
170
|
-
>
|
|
171
|
-
Link
|
|
172
|
-
</EvoButton>,
|
|
173
|
-
);
|
|
174
|
-
|
|
175
|
-
const link = screen.getByRole("link");
|
|
176
|
-
await user.click(link);
|
|
177
|
-
await user.keyboard("{Escape}");
|
|
178
|
-
|
|
179
|
-
expect(onEscape).toHaveBeenCalledTimes(1);
|
|
180
|
-
});
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
describe("body states", () => {
|
|
184
|
-
it("renders loading state with aria-live", async () => {
|
|
185
|
-
const screen = await render(
|
|
186
|
-
<EvoButton bodyState="loading">Submit</EvoButton>,
|
|
187
|
-
);
|
|
188
|
-
|
|
189
|
-
const button = screen.getByRole("button");
|
|
190
|
-
expect(button).toHaveAttribute("aria-live", "polite");
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
it("renders expand state with correct structure", async () => {
|
|
194
|
-
const screen = await render(
|
|
195
|
-
<EvoButton bodyState="expand">Options</EvoButton>,
|
|
196
|
-
);
|
|
197
|
-
|
|
198
|
-
const text = screen.getByText("Options");
|
|
199
|
-
await expect.element(text).toBeInTheDocument();
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
it("maintains interactivity in loading state", async () => {
|
|
203
|
-
const onClick = vi.fn();
|
|
204
|
-
const screen = await render(
|
|
205
|
-
<EvoButton bodyState="loading" onClick={onClick}>
|
|
206
|
-
Submit
|
|
207
|
-
</EvoButton>,
|
|
208
|
-
);
|
|
209
|
-
|
|
210
|
-
await user.click(screen.getByRole("button"));
|
|
211
|
-
|
|
212
|
-
expect(onClick).toHaveBeenCalledTimes(1);
|
|
213
|
-
});
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
describe("partially disabled state", () => {
|
|
217
|
-
it("renders with aria-disabled but remains clickable", async () => {
|
|
218
|
-
const onClick = vi.fn();
|
|
219
|
-
const screen = await render(
|
|
220
|
-
<EvoButton partiallyDisabled onClick={onClick}>
|
|
221
|
-
Partially Disabled
|
|
222
|
-
</EvoButton>,
|
|
223
|
-
);
|
|
224
|
-
|
|
225
|
-
const button = screen.getByRole("button");
|
|
226
|
-
await expect.element(button).toHaveAttribute("aria-disabled", "true");
|
|
227
|
-
|
|
228
|
-
await button.click({ force: true });
|
|
229
|
-
expect(onClick).toHaveBeenCalledTimes(1);
|
|
230
|
-
});
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
describe("keyboard navigation", () => {
|
|
234
|
-
it("can be focused via keyboard", async () => {
|
|
235
|
-
const onFocus = vi.fn();
|
|
236
|
-
await render(<EvoButton onFocus={onFocus}>Button</EvoButton>);
|
|
237
|
-
|
|
238
|
-
await user.tab();
|
|
239
|
-
|
|
240
|
-
expect(onFocus).toHaveBeenCalledTimes(1);
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
it("emits onKeyDown for all keys", async () => {
|
|
244
|
-
const onKeyDown = vi.fn();
|
|
245
|
-
const screen = await render(
|
|
246
|
-
<EvoButton onKeyDown={onKeyDown}>Button</EvoButton>,
|
|
247
|
-
);
|
|
248
|
-
|
|
249
|
-
const button = screen.getByRole("button");
|
|
250
|
-
await user.click(button);
|
|
251
|
-
await user.keyboard("{Enter}");
|
|
252
|
-
|
|
253
|
-
expect(onKeyDown).toHaveBeenCalledTimes(1);
|
|
254
|
-
});
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
describe("button cell", () => {
|
|
258
|
-
it("renders custom layout with ButtonCell", async () => {
|
|
259
|
-
const onClick = vi.fn();
|
|
260
|
-
const screen = await render(
|
|
261
|
-
<EvoButton onClick={onClick}>
|
|
262
|
-
<EvoButtonCell style={{ justifyContent: "space-between" }}>
|
|
263
|
-
<span>Left</span>
|
|
264
|
-
<span>Right</span>
|
|
265
|
-
</EvoButtonCell>
|
|
266
|
-
</EvoButton>,
|
|
267
|
-
);
|
|
268
|
-
|
|
269
|
-
await expect.element(screen.getByText("Left")).toBeInTheDocument();
|
|
270
|
-
await expect.element(screen.getByText("Right")).toBeInTheDocument();
|
|
271
|
-
|
|
272
|
-
await user.click(screen.getByRole("button"));
|
|
273
|
-
expect(onClick).toHaveBeenCalledTimes(1);
|
|
274
|
-
});
|
|
275
|
-
});
|
|
276
|
-
|
|
277
|
-
describe("accessibility", () => {
|
|
278
|
-
it("has correct role for button", async () => {
|
|
279
|
-
const screen = await render(<EvoButton>Button</EvoButton>);
|
|
280
|
-
await expect.element(screen.getByRole("button")).toBeInTheDocument();
|
|
281
|
-
});
|
|
282
|
-
|
|
283
|
-
it("has correct role for link", async () => {
|
|
284
|
-
const screen = await render(
|
|
285
|
-
<EvoButton href="https://ebay.com">Link</EvoButton>,
|
|
286
|
-
);
|
|
287
|
-
await expect.element(screen.getByRole("link")).toBeInTheDocument();
|
|
288
|
-
});
|
|
289
|
-
|
|
290
|
-
it("supports aria-label", async () => {
|
|
291
|
-
const screen = await render(
|
|
292
|
-
<EvoButton aria-label="Submit form">Submit</EvoButton>,
|
|
293
|
-
);
|
|
294
|
-
const button = screen.getByRole("button", { name: "Submit form" });
|
|
295
|
-
await expect.element(button).toBeInTheDocument();
|
|
296
|
-
});
|
|
297
|
-
});
|
|
298
|
-
});
|