@opensaas/stack-ui 0.1.7 → 0.3.0
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/.turbo/turbo-build.log +3 -2
- package/CHANGELOG.md +4 -0
- package/dist/components/AdminUI.d.ts +2 -1
- package/dist/components/AdminUI.d.ts.map +1 -1
- package/dist/components/AdminUI.js +2 -2
- package/dist/components/ItemFormClient.d.ts.map +1 -1
- package/dist/components/ItemFormClient.js +78 -60
- package/dist/components/Navigation.d.ts +2 -1
- package/dist/components/Navigation.d.ts.map +1 -1
- package/dist/components/Navigation.js +3 -2
- package/dist/components/UserMenu.d.ts +11 -0
- package/dist/components/UserMenu.d.ts.map +1 -0
- package/dist/components/UserMenu.js +18 -0
- package/dist/components/fields/TextField.d.ts +2 -1
- package/dist/components/fields/TextField.d.ts.map +1 -1
- package/dist/components/fields/TextField.js +4 -2
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/primitives/button.d.ts +1 -1
- package/dist/styles/globals.css +24 -0
- package/package.json +14 -5
- package/src/components/AdminUI.tsx +3 -0
- package/src/components/ItemFormClient.tsx +84 -62
- package/src/components/Navigation.tsx +9 -20
- package/src/components/UserMenu.tsx +44 -0
- package/src/components/fields/TextField.tsx +7 -2
- package/src/index.ts +2 -0
- package/tests/browser/README.md +154 -0
- package/tests/browser/fields/CheckboxField.browser.test.tsx +245 -0
- package/tests/browser/fields/SelectField.browser.test.tsx +263 -0
- package/tests/browser/fields/TextField.browser.test.tsx +204 -0
- package/tests/browser/fields/__screenshots__/CheckboxField.browser.test.tsx/CheckboxField--Browser--edit-mode-should-not-be-clickable-when-disabled-1.png +0 -0
- package/tests/browser/fields/__screenshots__/CheckboxField.browser.test.tsx/CheckboxField--Browser--edit-mode-should-toggle-state-with-multiple-clicks-1.png +0 -0
- package/tests/browser/fields/__screenshots__/TextField.browser.test.tsx/TextField--Browser--edit-mode-should-handle-special-characters-1.png +0 -0
- package/tests/browser/fields/__screenshots__/TextField.browser.test.tsx/TextField--Browser--edit-mode-should-support-copy-and-paste-1.png +0 -0
- package/tests/browser/primitives/Button.browser.test.tsx +122 -0
- package/tests/browser/primitives/Dialog.browser.test.tsx +279 -0
- package/tests/browser/primitives/__screenshots__/Button.browser.test.tsx/Button--Browser--should-not-trigger-click-when-disabled-1.png +0 -0
- package/tests/components/CheckboxField.test.tsx +130 -0
- package/tests/components/DeleteButton.test.tsx +331 -0
- package/tests/components/IntegerField.test.tsx +147 -0
- package/tests/components/ListTable.test.tsx +457 -0
- package/tests/components/ListViewClient.test.tsx +415 -0
- package/tests/components/SearchBar.test.tsx +254 -0
- package/tests/components/SelectField.test.tsx +192 -0
- package/vitest.config.ts +20 -0
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { render, screen } from '@testing-library/react'
|
|
3
|
+
import { userEvent } from 'vitest/browser'
|
|
4
|
+
import { Button } from '../../../src/primitives/button.js'
|
|
5
|
+
|
|
6
|
+
describe('Button (Browser)', () => {
|
|
7
|
+
it('should render with default variant', async () => {
|
|
8
|
+
render(<Button>Click me</Button>)
|
|
9
|
+
|
|
10
|
+
const button = screen.getByRole('button', { name: 'Click me' })
|
|
11
|
+
expect(button).toBeInTheDocument()
|
|
12
|
+
expect(button).toHaveClass('bg-primary')
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
it('should handle click events', async () => {
|
|
16
|
+
let clicked = false
|
|
17
|
+
const handleClick = () => {
|
|
18
|
+
clicked = true
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
render(<Button onClick={handleClick}>Click me</Button>)
|
|
22
|
+
|
|
23
|
+
const button = screen.getByRole('button', { name: 'Click me' })
|
|
24
|
+
await userEvent.click(button)
|
|
25
|
+
|
|
26
|
+
expect(clicked).toBe(true)
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it('should render different variants correctly', async () => {
|
|
30
|
+
const { rerender } = render(<Button variant="destructive">Delete</Button>)
|
|
31
|
+
let button = screen.getByRole('button', { name: 'Delete' })
|
|
32
|
+
expect(button).toHaveClass('bg-destructive')
|
|
33
|
+
|
|
34
|
+
rerender(<Button variant="outline">Cancel</Button>)
|
|
35
|
+
button = screen.getByRole('button', { name: 'Cancel' })
|
|
36
|
+
expect(button).toHaveClass('border')
|
|
37
|
+
|
|
38
|
+
rerender(<Button variant="ghost">Ghost</Button>)
|
|
39
|
+
button = screen.getByRole('button', { name: 'Ghost' })
|
|
40
|
+
expect(button).toHaveClass('hover:bg-accent')
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it('should render different sizes correctly', async () => {
|
|
44
|
+
const { rerender } = render(<Button size="sm">Small</Button>)
|
|
45
|
+
let button = screen.getByRole('button', { name: 'Small' })
|
|
46
|
+
expect(button).toHaveClass('h-9')
|
|
47
|
+
|
|
48
|
+
rerender(<Button size="lg">Large</Button>)
|
|
49
|
+
button = screen.getByRole('button', { name: 'Large' })
|
|
50
|
+
expect(button).toHaveClass('h-11')
|
|
51
|
+
|
|
52
|
+
rerender(<Button size="icon">Icon</Button>)
|
|
53
|
+
button = screen.getByRole('button', { name: 'Icon' })
|
|
54
|
+
expect(button).toHaveClass('h-10', 'w-10')
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it('should be disabled when disabled prop is true', async () => {
|
|
58
|
+
render(<Button disabled>Disabled</Button>)
|
|
59
|
+
|
|
60
|
+
const button = screen.getByRole('button', { name: 'Disabled' })
|
|
61
|
+
expect(button).toBeDisabled()
|
|
62
|
+
expect(button).toHaveClass('disabled:opacity-50')
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('should not trigger click when disabled', async () => {
|
|
66
|
+
let clicked = false
|
|
67
|
+
const handleClick = () => {
|
|
68
|
+
clicked = true
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
render(
|
|
72
|
+
<Button disabled onClick={handleClick}>
|
|
73
|
+
Disabled
|
|
74
|
+
</Button>,
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
const button = screen.getByRole('button', { name: 'Disabled' })
|
|
78
|
+
|
|
79
|
+
// Verify button is disabled - browsers prevent clicking disabled buttons
|
|
80
|
+
expect(button).toBeDisabled()
|
|
81
|
+
expect(button).toHaveClass('disabled:pointer-events-none')
|
|
82
|
+
|
|
83
|
+
// In real browsers, disabled buttons cannot be clicked
|
|
84
|
+
// The pointer-events-none class prevents any interaction
|
|
85
|
+
expect(clicked).toBe(false)
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
it('should handle keyboard navigation', async () => {
|
|
89
|
+
let clicked = false
|
|
90
|
+
const handleClick = () => {
|
|
91
|
+
clicked = true
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
render(<Button onClick={handleClick}>Press Enter</Button>)
|
|
95
|
+
|
|
96
|
+
const button = screen.getByRole('button', { name: 'Press Enter' })
|
|
97
|
+
button.focus()
|
|
98
|
+
expect(document.activeElement).toBe(button)
|
|
99
|
+
|
|
100
|
+
await userEvent.keyboard('{Enter}')
|
|
101
|
+
expect(clicked).toBe(true)
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
it('should support custom className', async () => {
|
|
105
|
+
render(<Button className="custom-class">Custom</Button>)
|
|
106
|
+
|
|
107
|
+
const button = screen.getByRole('button', { name: 'Custom' })
|
|
108
|
+
expect(button).toHaveClass('custom-class')
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
it('should render as child component when asChild is true', async () => {
|
|
112
|
+
render(
|
|
113
|
+
<Button asChild>
|
|
114
|
+
<a href="/test">Link Button</a>
|
|
115
|
+
</Button>,
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
const link = screen.getByRole('link', { name: 'Link Button' })
|
|
119
|
+
expect(link).toBeInTheDocument()
|
|
120
|
+
expect(link).toHaveAttribute('href', '/test')
|
|
121
|
+
})
|
|
122
|
+
})
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { render, screen, waitFor, act } from '@testing-library/react'
|
|
3
|
+
import { userEvent } from 'vitest/browser'
|
|
4
|
+
import React from 'react'
|
|
5
|
+
import {
|
|
6
|
+
Dialog,
|
|
7
|
+
DialogTrigger,
|
|
8
|
+
DialogContent,
|
|
9
|
+
DialogHeader,
|
|
10
|
+
DialogTitle,
|
|
11
|
+
DialogDescription,
|
|
12
|
+
DialogFooter,
|
|
13
|
+
DialogClose,
|
|
14
|
+
} from '../../../src/primitives/dialog.js'
|
|
15
|
+
import { Button } from '../../../src/primitives/button.js'
|
|
16
|
+
|
|
17
|
+
describe('Dialog (Browser)', () => {
|
|
18
|
+
it('should render dialog trigger', async () => {
|
|
19
|
+
await act(async () => {
|
|
20
|
+
render(
|
|
21
|
+
<Dialog>
|
|
22
|
+
<DialogTrigger asChild>
|
|
23
|
+
<Button>Open Dialog</Button>
|
|
24
|
+
</DialogTrigger>
|
|
25
|
+
</Dialog>,
|
|
26
|
+
)
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
const trigger = screen.getByRole('button', { name: 'Open Dialog' })
|
|
30
|
+
expect(trigger).toBeInTheDocument()
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it('should open dialog when trigger is clicked', async () => {
|
|
34
|
+
function TestDialog() {
|
|
35
|
+
const [open, setOpen] = React.useState(false)
|
|
36
|
+
return (
|
|
37
|
+
<Dialog open={open} onOpenChange={setOpen}>
|
|
38
|
+
<DialogTrigger asChild>
|
|
39
|
+
<Button>Open Dialog</Button>
|
|
40
|
+
</DialogTrigger>
|
|
41
|
+
<DialogContent>
|
|
42
|
+
<DialogHeader>
|
|
43
|
+
<DialogTitle>Dialog Title</DialogTitle>
|
|
44
|
+
<DialogDescription>Dialog description</DialogDescription>
|
|
45
|
+
</DialogHeader>
|
|
46
|
+
</DialogContent>
|
|
47
|
+
</Dialog>
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
await act(async () => {
|
|
52
|
+
render(<TestDialog />)
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
const trigger = screen.getByRole('button', { name: 'Open Dialog' })
|
|
56
|
+
|
|
57
|
+
await act(async () => {
|
|
58
|
+
await userEvent.click(trigger)
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
await waitFor(
|
|
62
|
+
() => {
|
|
63
|
+
expect(screen.getByRole('dialog')).toBeInTheDocument()
|
|
64
|
+
expect(screen.getByText('Dialog Title')).toBeInTheDocument()
|
|
65
|
+
expect(screen.getByText('Dialog description')).toBeInTheDocument()
|
|
66
|
+
},
|
|
67
|
+
{ timeout: 3000 },
|
|
68
|
+
)
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it('should close dialog when close button is clicked', async () => {
|
|
72
|
+
function TestDialog() {
|
|
73
|
+
const [open, setOpen] = React.useState(false)
|
|
74
|
+
return (
|
|
75
|
+
<Dialog open={open} onOpenChange={setOpen}>
|
|
76
|
+
<DialogTrigger asChild>
|
|
77
|
+
<Button>Open Dialog</Button>
|
|
78
|
+
</DialogTrigger>
|
|
79
|
+
<DialogContent>
|
|
80
|
+
<DialogHeader>
|
|
81
|
+
<DialogTitle>Dialog Title</DialogTitle>
|
|
82
|
+
</DialogHeader>
|
|
83
|
+
</DialogContent>
|
|
84
|
+
</Dialog>
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
await act(async () => {
|
|
89
|
+
render(<TestDialog />)
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
const trigger = screen.getByRole('button', { name: 'Open Dialog' })
|
|
93
|
+
|
|
94
|
+
await act(async () => {
|
|
95
|
+
await userEvent.click(trigger)
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
await waitFor(() => {
|
|
99
|
+
expect(screen.getByRole('dialog')).toBeInTheDocument()
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
// Click the X close button
|
|
103
|
+
const closeButton = screen.getByRole('button', { name: 'Close' })
|
|
104
|
+
|
|
105
|
+
await act(async () => {
|
|
106
|
+
await userEvent.click(closeButton)
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
await waitFor(() => {
|
|
110
|
+
expect(screen.queryByRole('dialog')).not.toBeInTheDocument()
|
|
111
|
+
})
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
it('should close dialog with DialogClose component', async () => {
|
|
115
|
+
function TestDialog() {
|
|
116
|
+
const [open, setOpen] = React.useState(false)
|
|
117
|
+
return (
|
|
118
|
+
<Dialog open={open} onOpenChange={setOpen}>
|
|
119
|
+
<DialogTrigger asChild>
|
|
120
|
+
<Button>Open Dialog</Button>
|
|
121
|
+
</DialogTrigger>
|
|
122
|
+
<DialogContent>
|
|
123
|
+
<DialogHeader>
|
|
124
|
+
<DialogTitle>Dialog Title</DialogTitle>
|
|
125
|
+
</DialogHeader>
|
|
126
|
+
<DialogFooter>
|
|
127
|
+
<DialogClose asChild>
|
|
128
|
+
<Button variant="outline">Cancel</Button>
|
|
129
|
+
</DialogClose>
|
|
130
|
+
</DialogFooter>
|
|
131
|
+
</DialogContent>
|
|
132
|
+
</Dialog>
|
|
133
|
+
)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
await act(async () => {
|
|
137
|
+
render(<TestDialog />)
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
const trigger = screen.getByRole('button', { name: 'Open Dialog' })
|
|
141
|
+
|
|
142
|
+
await act(async () => {
|
|
143
|
+
await userEvent.click(trigger)
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
await waitFor(() => {
|
|
147
|
+
expect(screen.getByRole('dialog')).toBeInTheDocument()
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
const cancelButton = screen.getByRole('button', { name: 'Cancel' })
|
|
151
|
+
|
|
152
|
+
await act(async () => {
|
|
153
|
+
await userEvent.click(cancelButton)
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
await waitFor(() => {
|
|
157
|
+
expect(screen.queryByRole('dialog')).not.toBeInTheDocument()
|
|
158
|
+
})
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
it('should close dialog when escape key is pressed', async () => {
|
|
162
|
+
function TestDialog() {
|
|
163
|
+
const [open, setOpen] = React.useState(false)
|
|
164
|
+
return (
|
|
165
|
+
<Dialog open={open} onOpenChange={setOpen}>
|
|
166
|
+
<DialogTrigger asChild>
|
|
167
|
+
<Button>Open Dialog</Button>
|
|
168
|
+
</DialogTrigger>
|
|
169
|
+
<DialogContent>
|
|
170
|
+
<DialogHeader>
|
|
171
|
+
<DialogTitle>Dialog Title</DialogTitle>
|
|
172
|
+
</DialogHeader>
|
|
173
|
+
</DialogContent>
|
|
174
|
+
</Dialog>
|
|
175
|
+
)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
await act(async () => {
|
|
179
|
+
render(<TestDialog />)
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
const trigger = screen.getByRole('button', { name: 'Open Dialog' })
|
|
183
|
+
|
|
184
|
+
await act(async () => {
|
|
185
|
+
await userEvent.click(trigger)
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
await waitFor(() => {
|
|
189
|
+
expect(screen.getByRole('dialog')).toBeInTheDocument()
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
await act(async () => {
|
|
193
|
+
await userEvent.keyboard('{Escape}')
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
await waitFor(() => {
|
|
197
|
+
expect(screen.queryByRole('dialog')).not.toBeInTheDocument()
|
|
198
|
+
})
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
it('should render overlay when dialog is open', async () => {
|
|
202
|
+
function TestDialog() {
|
|
203
|
+
const [open, setOpen] = React.useState(false)
|
|
204
|
+
return (
|
|
205
|
+
<Dialog open={open} onOpenChange={setOpen}>
|
|
206
|
+
<DialogTrigger asChild>
|
|
207
|
+
<Button>Open Dialog</Button>
|
|
208
|
+
</DialogTrigger>
|
|
209
|
+
<DialogContent>
|
|
210
|
+
<DialogHeader>
|
|
211
|
+
<DialogTitle>Dialog Title</DialogTitle>
|
|
212
|
+
</DialogHeader>
|
|
213
|
+
</DialogContent>
|
|
214
|
+
</Dialog>
|
|
215
|
+
)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
await act(async () => {
|
|
219
|
+
render(<TestDialog />)
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
const trigger = screen.getByRole('button', { name: 'Open Dialog' })
|
|
223
|
+
|
|
224
|
+
await act(async () => {
|
|
225
|
+
await userEvent.click(trigger)
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
await waitFor(() => {
|
|
229
|
+
const overlay = document.querySelector('[data-state="open"]')
|
|
230
|
+
expect(overlay).toBeInTheDocument()
|
|
231
|
+
})
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
it('should trap focus within dialog when open', async () => {
|
|
235
|
+
function TestDialog() {
|
|
236
|
+
const [open, setOpen] = React.useState(false)
|
|
237
|
+
return (
|
|
238
|
+
<Dialog open={open} onOpenChange={setOpen}>
|
|
239
|
+
<DialogTrigger asChild>
|
|
240
|
+
<Button>Open Dialog</Button>
|
|
241
|
+
</DialogTrigger>
|
|
242
|
+
<DialogContent>
|
|
243
|
+
<DialogHeader>
|
|
244
|
+
<DialogTitle>Dialog Title</DialogTitle>
|
|
245
|
+
</DialogHeader>
|
|
246
|
+
<DialogFooter>
|
|
247
|
+
<Button>First Button</Button>
|
|
248
|
+
<Button>Second Button</Button>
|
|
249
|
+
</DialogFooter>
|
|
250
|
+
</DialogContent>
|
|
251
|
+
</Dialog>
|
|
252
|
+
)
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
await act(async () => {
|
|
256
|
+
render(<TestDialog />)
|
|
257
|
+
})
|
|
258
|
+
|
|
259
|
+
const trigger = screen.getByRole('button', { name: 'Open Dialog' })
|
|
260
|
+
|
|
261
|
+
await act(async () => {
|
|
262
|
+
await userEvent.click(trigger)
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
await waitFor(() => {
|
|
266
|
+
expect(screen.getByRole('dialog')).toBeInTheDocument()
|
|
267
|
+
})
|
|
268
|
+
|
|
269
|
+
// Tab through focusable elements
|
|
270
|
+
await act(async () => {
|
|
271
|
+
await userEvent.keyboard('{Tab}')
|
|
272
|
+
})
|
|
273
|
+
|
|
274
|
+
// Focus should be within the dialog
|
|
275
|
+
const dialog = screen.getByRole('dialog')
|
|
276
|
+
const activeElement = document.activeElement
|
|
277
|
+
expect(dialog.contains(activeElement)).toBe(true)
|
|
278
|
+
})
|
|
279
|
+
})
|
|
Binary file
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest'
|
|
2
|
+
import { render, screen } from '@testing-library/react'
|
|
3
|
+
import userEvent from '@testing-library/user-event'
|
|
4
|
+
import { CheckboxField } from '../../src/components/fields/CheckboxField.js'
|
|
5
|
+
|
|
6
|
+
describe('CheckboxField', () => {
|
|
7
|
+
describe('edit mode', () => {
|
|
8
|
+
it('should render checkbox with label', () => {
|
|
9
|
+
render(<CheckboxField name="active" value={false} onChange={vi.fn()} label="Is Active" />)
|
|
10
|
+
|
|
11
|
+
expect(screen.getByLabelText('Is Active')).toBeInTheDocument()
|
|
12
|
+
expect(screen.getByRole('checkbox')).toBeInTheDocument()
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
it('should display checked state when value is true', () => {
|
|
16
|
+
render(<CheckboxField name="active" value={true} onChange={vi.fn()} label="Is Active" />)
|
|
17
|
+
|
|
18
|
+
const checkbox = screen.getByRole('checkbox')
|
|
19
|
+
expect(checkbox).toBeChecked()
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
it('should display unchecked state when value is false', () => {
|
|
23
|
+
render(<CheckboxField name="active" value={false} onChange={vi.fn()} label="Is Active" />)
|
|
24
|
+
|
|
25
|
+
const checkbox = screen.getByRole('checkbox')
|
|
26
|
+
expect(checkbox).not.toBeChecked()
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it('should call onChange with true when clicked', async () => {
|
|
30
|
+
const onChange = vi.fn()
|
|
31
|
+
const user = userEvent.setup()
|
|
32
|
+
|
|
33
|
+
render(<CheckboxField name="active" value={false} onChange={onChange} label="Is Active" />)
|
|
34
|
+
|
|
35
|
+
const checkbox = screen.getByRole('checkbox')
|
|
36
|
+
await user.click(checkbox)
|
|
37
|
+
|
|
38
|
+
expect(onChange).toHaveBeenCalledWith(true)
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it('should call onChange with false when unchecked', async () => {
|
|
42
|
+
const onChange = vi.fn()
|
|
43
|
+
const user = userEvent.setup()
|
|
44
|
+
|
|
45
|
+
render(<CheckboxField name="active" value={true} onChange={onChange} label="Is Active" />)
|
|
46
|
+
|
|
47
|
+
const checkbox = screen.getByRole('checkbox')
|
|
48
|
+
await user.click(checkbox)
|
|
49
|
+
|
|
50
|
+
expect(onChange).toHaveBeenCalledWith(false)
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it('should display error message', () => {
|
|
54
|
+
render(
|
|
55
|
+
<CheckboxField
|
|
56
|
+
name="terms"
|
|
57
|
+
value={false}
|
|
58
|
+
onChange={vi.fn()}
|
|
59
|
+
label="Accept Terms"
|
|
60
|
+
error="You must accept the terms"
|
|
61
|
+
/>,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
expect(screen.getByText('You must accept the terms')).toBeInTheDocument()
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it('should be disabled when disabled prop is true', () => {
|
|
68
|
+
render(
|
|
69
|
+
<CheckboxField name="active" value={false} onChange={vi.fn()} label="Is Active" disabled />,
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
const checkbox = screen.getByRole('checkbox')
|
|
73
|
+
expect(checkbox).toBeDisabled()
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it('should not be clickable when disabled', async () => {
|
|
77
|
+
const onChange = vi.fn()
|
|
78
|
+
const user = userEvent.setup()
|
|
79
|
+
|
|
80
|
+
render(
|
|
81
|
+
<CheckboxField
|
|
82
|
+
name="active"
|
|
83
|
+
value={false}
|
|
84
|
+
onChange={onChange}
|
|
85
|
+
label="Is Active"
|
|
86
|
+
disabled
|
|
87
|
+
/>,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
const checkbox = screen.getByRole('checkbox')
|
|
91
|
+
await user.click(checkbox)
|
|
92
|
+
|
|
93
|
+
expect(onChange).not.toHaveBeenCalled()
|
|
94
|
+
})
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
describe('read mode', () => {
|
|
98
|
+
it('should render "Yes" when value is true', () => {
|
|
99
|
+
render(
|
|
100
|
+
<CheckboxField
|
|
101
|
+
name="active"
|
|
102
|
+
value={true}
|
|
103
|
+
onChange={vi.fn()}
|
|
104
|
+
label="Is Active"
|
|
105
|
+
mode="read"
|
|
106
|
+
/>,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
expect(screen.getByText('Is Active')).toBeInTheDocument()
|
|
110
|
+
expect(screen.getByText('Yes')).toBeInTheDocument()
|
|
111
|
+
expect(screen.queryByRole('checkbox')).not.toBeInTheDocument()
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
it('should render "No" when value is false', () => {
|
|
115
|
+
render(
|
|
116
|
+
<CheckboxField
|
|
117
|
+
name="active"
|
|
118
|
+
value={false}
|
|
119
|
+
onChange={vi.fn()}
|
|
120
|
+
label="Is Active"
|
|
121
|
+
mode="read"
|
|
122
|
+
/>,
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
expect(screen.getByText('Is Active')).toBeInTheDocument()
|
|
126
|
+
expect(screen.getByText('No')).toBeInTheDocument()
|
|
127
|
+
expect(screen.queryByRole('checkbox')).not.toBeInTheDocument()
|
|
128
|
+
})
|
|
129
|
+
})
|
|
130
|
+
})
|