1
+ import React from 'react'
1
2
import { render , screen , fireEvent } from '@testing-library/react'
2
3
import { describe , it , expect , vi } from 'vitest'
3
4
import userEvent from '@testing-library/user-event'
5
+ import '@testing-library/jest-dom'
4
6
import { Button } from '../button'
5
7
6
8
describe ( 'Button' , ( ) => {
7
9
it ( 'renders button with children' , ( ) => {
8
10
render ( < Button > Click me</ Button > )
9
-
11
+
10
12
expect ( screen . getByRole ( 'button' ) ) . toBeInTheDocument ( )
11
13
expect ( screen . getByText ( 'Click me' ) ) . toBeInTheDocument ( )
12
14
} )
13
15
14
16
it ( 'applies default variant classes' , ( ) => {
15
17
render ( < Button > Default Button</ Button > )
16
-
18
+
17
19
const button = screen . getByRole ( 'button' )
18
- expect ( button ) . toHaveClass ( 'bg-primary' , 'text-primary-fg' , 'hover:bg-primary/90' )
20
+ expect ( button ) . toHaveClass (
21
+ 'bg-primary' ,
22
+ 'text-primary-fg' ,
23
+ 'hover:bg-primary/90'
24
+ )
19
25
} )
20
26
21
27
it ( 'applies destructive variant classes' , ( ) => {
22
28
render ( < Button variant = "destructive" > Destructive Button</ Button > )
23
-
29
+
24
30
const button = screen . getByRole ( 'button' )
25
- expect ( button ) . toHaveClass ( 'bg-destructive' , 'text-destructive-fg' , 'hover:bg-destructive/90' )
31
+ expect ( button ) . toHaveClass (
32
+ 'bg-destructive' ,
33
+ 'text-destructive-fg' ,
34
+ 'hover:bg-destructive/90'
35
+ )
26
36
} )
27
37
28
38
it ( 'applies link variant classes' , ( ) => {
29
39
render ( < Button variant = "link" > Link Button</ Button > )
30
-
40
+
31
41
const button = screen . getByRole ( 'button' )
32
42
expect ( button ) . toHaveClass ( 'underline-offset-4' , 'hover:no-underline' )
33
43
} )
34
44
35
45
it ( 'applies default size classes' , ( ) => {
36
46
render ( < Button > Default Size</ Button > )
37
-
47
+
38
48
const button = screen . getByRole ( 'button' )
39
49
expect ( button ) . toHaveClass ( 'h-7' , 'px-3' , 'py-2' )
40
50
} )
41
51
42
52
it ( 'applies small size classes' , ( ) => {
43
53
render ( < Button size = "sm" > Small Button</ Button > )
44
-
54
+
45
55
const button = screen . getByRole ( 'button' )
46
56
expect ( button ) . toHaveClass ( 'h-6' , 'px-2' )
47
57
} )
48
58
49
59
it ( 'applies large size classes' , ( ) => {
50
60
render ( < Button size = "lg" > Large Button</ Button > )
51
-
61
+
52
62
const button = screen . getByRole ( 'button' )
53
63
expect ( button ) . toHaveClass ( 'h-9' , 'rounded-md' , 'px-4' )
54
64
} )
55
65
56
66
it ( 'applies icon size classes' , ( ) => {
57
67
render ( < Button size = "icon" > Icon</ Button > )
58
-
68
+
59
69
const button = screen . getByRole ( 'button' )
60
70
expect ( button ) . toHaveClass ( 'size-8' )
61
71
} )
62
72
63
73
it ( 'handles click events' , async ( ) => {
64
74
const handleClick = vi . fn ( )
65
75
const user = userEvent . setup ( )
66
-
76
+
67
77
render ( < Button onClick = { handleClick } > Click me</ Button > )
68
-
78
+
69
79
await user . click ( screen . getByRole ( 'button' ) )
70
-
80
+
71
81
expect ( handleClick ) . toHaveBeenCalledTimes ( 1 )
72
82
} )
73
83
74
84
it ( 'can be disabled' , ( ) => {
75
85
render ( < Button disabled > Disabled Button</ Button > )
76
-
86
+
77
87
const button = screen . getByRole ( 'button' )
78
88
expect ( button ) . toBeDisabled ( )
79
- expect ( button ) . toHaveClass ( 'disabled:pointer-events-none' , 'disabled:opacity-50' )
89
+ expect ( button ) . toHaveClass (
90
+ 'disabled:pointer-events-none' ,
91
+ 'disabled:opacity-50'
92
+ )
80
93
} )
81
94
82
95
it ( 'does not trigger click when disabled' , async ( ) => {
83
96
const handleClick = vi . fn ( )
84
97
const user = userEvent . setup ( )
85
-
86
- render ( < Button disabled onClick = { handleClick } > Disabled Button</ Button > )
87
-
98
+
99
+ render (
100
+ < Button disabled onClick = { handleClick } >
101
+ Disabled Button
102
+ </ Button >
103
+ )
104
+
88
105
await user . click ( screen . getByRole ( 'button' ) )
89
-
106
+
90
107
expect ( handleClick ) . not . toHaveBeenCalled ( )
91
108
} )
92
109
93
110
it ( 'forwards ref correctly' , ( ) => {
94
111
const ref = vi . fn ( )
95
-
112
+
96
113
render ( < Button ref = { ref } > Button with ref</ Button > )
97
-
114
+
98
115
expect ( ref ) . toHaveBeenCalledWith ( expect . any ( HTMLButtonElement ) )
99
116
} )
100
117
101
118
it ( 'accepts custom className' , ( ) => {
102
119
render ( < Button className = "custom-class" > Custom Button</ Button > )
103
-
120
+
104
121
const button = screen . getByRole ( 'button' )
105
122
expect ( button ) . toHaveClass ( 'custom-class' )
106
123
} )
107
124
108
125
it ( 'accepts custom props' , ( ) => {
109
- render ( < Button data-testid = "custom-button" type = "submit" > Custom Button</ Button > )
110
-
126
+ render (
127
+ < Button data-testid = "custom-button" type = "submit" >
128
+ Custom Button
129
+ </ Button >
130
+ )
131
+
111
132
const button = screen . getByTestId ( 'custom-button' )
112
133
expect ( button ) . toHaveAttribute ( 'type' , 'submit' )
113
134
} )
@@ -118,51 +139,65 @@ describe('Button', () => {
118
139
< a href = "/test" > Link Button</ a >
119
140
</ Button >
120
141
)
121
-
142
+
122
143
const link = screen . getByRole ( 'link' )
123
144
expect ( link ) . toHaveAttribute ( 'href' , '/test' )
124
145
expect ( link ) . toHaveClass ( 'bg-primary' , 'text-primary-fg' ) // Should inherit button classes
125
146
} )
126
147
127
148
it ( 'combines variant and size classes correctly' , ( ) => {
128
- render ( < Button variant = "destructive" size = "lg" > Large Destructive Button</ Button > )
129
-
149
+ render (
150
+ < Button variant = "destructive" size = "lg" >
151
+ Large Destructive Button
152
+ </ Button >
153
+ )
154
+
130
155
const button = screen . getByRole ( 'button' )
131
156
expect ( button ) . toHaveClass ( 'bg-destructive' , 'text-destructive-fg' ) // destructive variant
132
157
expect ( button ) . toHaveClass ( 'h-9' , 'rounded-md' , 'px-4' ) // large size
133
158
} )
134
159
135
160
it ( 'handles keyboard events' , ( ) => {
136
161
const handleKeyDown = vi . fn ( )
137
-
162
+
138
163
render ( < Button onKeyDown = { handleKeyDown } > Keyboard Button</ Button > )
139
-
164
+
140
165
const button = screen . getByRole ( 'button' )
141
166
fireEvent . keyDown ( button , { key : 'Enter' } )
142
-
143
- expect ( handleKeyDown ) . toHaveBeenCalledWith ( expect . objectContaining ( {
144
- key : 'Enter'
145
- } ) )
167
+
168
+ expect ( handleKeyDown ) . toHaveBeenCalledWith (
169
+ expect . objectContaining ( {
170
+ key : 'Enter' ,
171
+ } )
172
+ )
146
173
} )
147
174
148
175
it ( 'supports focus events' , ( ) => {
149
176
const handleFocus = vi . fn ( )
150
177
const handleBlur = vi . fn ( )
151
-
152
- render ( < Button onFocus = { handleFocus } onBlur = { handleBlur } > Focus Button</ Button > )
153
-
178
+
179
+ render (
180
+ < Button onFocus = { handleFocus } onBlur = { handleBlur } >
181
+ Focus Button
182
+ </ Button >
183
+ )
184
+
154
185
const button = screen . getByRole ( 'button' )
155
186
fireEvent . focus ( button )
156
187
fireEvent . blur ( button )
157
-
188
+
158
189
expect ( handleFocus ) . toHaveBeenCalledTimes ( 1 )
159
190
expect ( handleBlur ) . toHaveBeenCalledTimes ( 1 )
160
191
} )
161
192
162
193
it ( 'applies focus-visible styling' , ( ) => {
163
194
render ( < Button > Focus Button</ Button > )
164
-
195
+
165
196
const button = screen . getByRole ( 'button' )
166
- expect ( button ) . toHaveClass ( 'focus-visible:border-ring' , 'focus-visible:ring-ring/50' )
197
+ expect ( button ) . toHaveClass (
198
+ 'focus-visible:border-primary' ,
199
+ 'focus-visible:ring-2' ,
200
+ 'focus-visible:ring-primary/60'
201
+ )
167
202
} )
168
- } )
203
+ } )
0 commit comments