@elizaos/client 1.5.5-alpha.10
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/LICENSE +21 -0
- package/README.md +350 -0
- package/dist/assets/empty-module-CLMscLYw.js +1 -0
- package/dist/assets/main-BBZ_3lkn.css +5999 -0
- package/dist/assets/main-C5zNUkXH.js +7 -0
- package/dist/assets/main-Dz64ENQg.js +614 -0
- package/dist/assets/react-vendor-DM5m98rr.js +545 -0
- package/dist/assets/ui-vendor-BQCqNqg0.js +1 -0
- package/dist/elizaos-avatar.png +0 -0
- package/dist/elizaos-icon.png +0 -0
- package/dist/elizaos-logo-light.png +0 -0
- package/dist/elizaos.webp +0 -0
- package/dist/favicon.ico +0 -0
- package/dist/images/agents/agent1.png +0 -0
- package/dist/images/agents/agent2.png +0 -0
- package/dist/images/agents/agent3.png +0 -0
- package/dist/images/agents/agent4.png +0 -0
- package/dist/images/agents/agent5.png +0 -0
- package/dist/index.html +14 -0
- package/index.html +24 -0
- package/package.json +159 -0
- package/postcss.config.js +3 -0
- package/public/elizaos-avatar.png +0 -0
- package/public/elizaos-icon.png +0 -0
- package/public/elizaos-logo-light.png +0 -0
- package/public/elizaos.webp +0 -0
- package/public/favicon.ico +0 -0
- package/public/images/agents/agent1.png +0 -0
- package/public/images/agents/agent2.png +0 -0
- package/public/images/agents/agent3.png +0 -0
- package/public/images/agents/agent4.png +0 -0
- package/public/images/agents/agent5.png +0 -0
- package/src/App.tsx +222 -0
- package/src/components/AgentDetailsPanel.tsx +147 -0
- package/src/components/ChatInputArea.tsx +196 -0
- package/src/components/ChatMessageListComponent.tsx +139 -0
- package/src/components/actionTool.tsx +186 -0
- package/src/components/add-agent-card.tsx +77 -0
- package/src/components/agent-action-viewer.tsx +816 -0
- package/src/components/agent-avatar-stack.tsx +121 -0
- package/src/components/agent-card.cy.tsx +259 -0
- package/src/components/agent-card.tsx +177 -0
- package/src/components/agent-creator.tsx +142 -0
- package/src/components/agent-log-viewer.tsx +645 -0
- package/src/components/agent-memory-edit-overlay.tsx +461 -0
- package/src/components/agent-memory-viewer.tsx +504 -0
- package/src/components/agent-settings.tsx +270 -0
- package/src/components/agent-sidebar.tsx +178 -0
- package/src/components/api-key-dialog.tsx +113 -0
- package/src/components/app-sidebar.tsx +685 -0
- package/src/components/array-input.tsx +116 -0
- package/src/components/audio-recorder.tsx +292 -0
- package/src/components/avatar-panel.tsx +141 -0
- package/src/components/character-form.tsx +1138 -0
- package/src/components/chat.tsx +1813 -0
- package/src/components/combobox.tsx +187 -0
- package/src/components/confirmation-dialog.tsx +59 -0
- package/src/components/connection-error-banner.tsx +101 -0
- package/src/components/connection-status.cy.tsx +73 -0
- package/src/components/connection-status.tsx +155 -0
- package/src/components/copy-button.tsx +35 -0
- package/src/components/delete-button.tsx +24 -0
- package/src/components/env-settings.tsx +261 -0
- package/src/components/group-card.tsx +160 -0
- package/src/components/group-panel.tsx +543 -0
- package/src/components/input-copy.tsx +21 -0
- package/src/components/logs-page.tsx +41 -0
- package/src/components/media-content.tsx +385 -0
- package/src/components/memory-graph.tsx +170 -0
- package/src/components/missing-secrets-dialog.tsx +72 -0
- package/src/components/onboarding-tour.tsx +247 -0
- package/src/components/page-title.tsx +8 -0
- package/src/components/plugins-panel.tsx +383 -0
- package/src/components/profile-card.tsx +66 -0
- package/src/components/profile-overlay.tsx +283 -0
- package/src/components/retry-button.tsx +28 -0
- package/src/components/secret-panel.tsx +1505 -0
- package/src/components/server-management.tsx +264 -0
- package/src/components/split-button.tsx +148 -0
- package/src/components/stop-agent-button.tsx +99 -0
- package/src/components/ui/alert-dialog.cy.tsx +333 -0
- package/src/components/ui/alert-dialog.tsx +115 -0
- package/src/components/ui/alert.tsx +49 -0
- package/src/components/ui/avatar.cy.tsx +180 -0
- package/src/components/ui/avatar.tsx +57 -0
- package/src/components/ui/badge.cy.tsx +146 -0
- package/src/components/ui/badge.tsx +43 -0
- package/src/components/ui/button.cy.tsx +177 -0
- package/src/components/ui/button.tsx +56 -0
- package/src/components/ui/card.cy.tsx +160 -0
- package/src/components/ui/card.tsx +73 -0
- package/src/components/ui/chat/animated-markdown.tsx +59 -0
- package/src/components/ui/chat/chat-bubble.tsx +178 -0
- package/src/components/ui/chat/chat-container.tsx +51 -0
- package/src/components/ui/chat/chat-input.cy.tsx +169 -0
- package/src/components/ui/chat/chat-input.tsx +47 -0
- package/src/components/ui/chat/chat-message-list.tsx +61 -0
- package/src/components/ui/chat/chat-tts-button.tsx +199 -0
- package/src/components/ui/chat/code-block.tsx +79 -0
- package/src/components/ui/chat/expandable-chat.tsx +131 -0
- package/src/components/ui/chat/hooks/useAutoScroll.ts +86 -0
- package/src/components/ui/chat/markdown.tsx +209 -0
- package/src/components/ui/chat/message-loading.tsx +48 -0
- package/src/components/ui/checkbox.cy.tsx +170 -0
- package/src/components/ui/checkbox.tsx +30 -0
- package/src/components/ui/collapsible.cy.tsx +283 -0
- package/src/components/ui/collapsible.tsx +9 -0
- package/src/components/ui/command.cy.tsx +313 -0
- package/src/components/ui/command.tsx +143 -0
- package/src/components/ui/dialog.cy.tsx +279 -0
- package/src/components/ui/dialog.tsx +104 -0
- package/src/components/ui/dropdown-menu.cy.tsx +273 -0
- package/src/components/ui/dropdown-menu.tsx +281 -0
- package/src/components/ui/input.cy.tsx +82 -0
- package/src/components/ui/input.tsx +27 -0
- package/src/components/ui/label.cy.tsx +157 -0
- package/src/components/ui/label.tsx +19 -0
- package/src/components/ui/resizable.tsx +42 -0
- package/src/components/ui/scroll-area.cy.tsx +242 -0
- package/src/components/ui/scroll-area.tsx +46 -0
- package/src/components/ui/select.cy.tsx +277 -0
- package/src/components/ui/select.tsx +155 -0
- package/src/components/ui/separator.cy.tsx +145 -0
- package/src/components/ui/separator.tsx +29 -0
- package/src/components/ui/sheet.cy.tsx +324 -0
- package/src/components/ui/sheet.tsx +119 -0
- package/src/components/ui/sidebar.tsx +734 -0
- package/src/components/ui/skeleton.cy.tsx +149 -0
- package/src/components/ui/skeleton.tsx +17 -0
- package/src/components/ui/split-button.cy.tsx +274 -0
- package/src/components/ui/split-button.tsx +112 -0
- package/src/components/ui/switch.tsx +28 -0
- package/src/components/ui/tabs.cy.tsx +271 -0
- package/src/components/ui/tabs.tsx +53 -0
- package/src/components/ui/textarea.cy.tsx +136 -0
- package/src/components/ui/textarea.tsx +26 -0
- package/src/components/ui/toast.cy.tsx +209 -0
- package/src/components/ui/toast.tsx +126 -0
- package/src/components/ui/toaster.tsx +29 -0
- package/src/components/ui/tooltip.cy.tsx +244 -0
- package/src/components/ui/tooltip.tsx +30 -0
- package/src/config/agent-templates.ts +349 -0
- package/src/config/voice-models.ts +181 -0
- package/src/constants.ts +23 -0
- package/src/context/AuthContext.tsx +44 -0
- package/src/context/ConnectionContext.tsx +194 -0
- package/src/entry.tsx +9 -0
- package/src/hooks/__tests__/use-agent-tab-state.test.ts +137 -0
- package/src/hooks/__tests__/use-agent-update.test.tsx +250 -0
- package/src/hooks/__tests__/use-character-convert.test.ts +102 -0
- package/src/hooks/__tests__/use-panel-width-state.test.ts +243 -0
- package/src/hooks/__tests__/use-sidebar-state.test.ts +117 -0
- package/src/hooks/use-agent-management.ts +130 -0
- package/src/hooks/use-agent-tab-state.ts +74 -0
- package/src/hooks/use-agent-update.ts +469 -0
- package/src/hooks/use-character-convert.ts +138 -0
- package/src/hooks/use-confirmation.ts +55 -0
- package/src/hooks/use-delete-agent.ts +123 -0
- package/src/hooks/use-dm-channels.ts +198 -0
- package/src/hooks/use-elevenlabs-voices.ts +83 -0
- package/src/hooks/use-file-upload.ts +224 -0
- package/src/hooks/use-mobile.tsx +19 -0
- package/src/hooks/use-onboarding.tsx +49 -0
- package/src/hooks/use-panel-width-state.ts +147 -0
- package/src/hooks/use-partial-update.ts +288 -0
- package/src/hooks/use-plugin-details.ts +462 -0
- package/src/hooks/use-plugins.ts +119 -0
- package/src/hooks/use-query-hooks.ts +1263 -0
- package/src/hooks/use-server-agents.ts +62 -0
- package/src/hooks/use-server-version.tsx +47 -0
- package/src/hooks/use-sidebar-state.ts +50 -0
- package/src/hooks/use-socket-chat.ts +264 -0
- package/src/hooks/use-toast.ts +260 -0
- package/src/hooks/use-version.tsx +64 -0
- package/src/index.css +146 -0
- package/src/lib/api-client-config.ts +53 -0
- package/src/lib/api-type-mappers.ts +196 -0
- package/src/lib/export-utils.ts +123 -0
- package/src/lib/logger.ts +19 -0
- package/src/lib/media-utils.ts +170 -0
- package/src/lib/pca.test.ts +17 -0
- package/src/lib/pca.ts +52 -0
- package/src/lib/socketio-manager.ts +664 -0
- package/src/lib/utils.ts +168 -0
- package/src/main.tsx +16 -0
- package/src/mocks/empty-module.ts +12 -0
- package/src/mocks/node-module.ts +57 -0
- package/src/polyfills.ts +37 -0
- package/src/routes/agent-detail.tsx +30 -0
- package/src/routes/agent-list.tsx +27 -0
- package/src/routes/agent-settings.tsx +48 -0
- package/src/routes/character-detail.tsx +52 -0
- package/src/routes/character-form.tsx +79 -0
- package/src/routes/character-list.tsx +38 -0
- package/src/routes/chat.tsx +128 -0
- package/src/routes/createAgent.tsx +13 -0
- package/src/routes/group-new.tsx +50 -0
- package/src/routes/group.tsx +29 -0
- package/src/routes/home.tsx +218 -0
- package/src/routes/not-found.tsx +71 -0
- package/src/test/setup.ts +154 -0
- package/src/types/crypto-browserify.d.ts +4 -0
- package/src/types/index.ts +13 -0
- package/src/types/rooms.ts +8 -0
- package/src/types.ts +84 -0
- package/src/vite-env.d.ts +40 -0
- package/tailwind.config.ts +90 -0
- package/tsconfig.json +10 -0
- package/vite.config.ts +102 -0
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
/// <reference types="cypress" />
|
|
2
|
+
/// <reference path="../../../cypress/support/types.d.ts" />
|
|
3
|
+
|
|
4
|
+
import React from 'react';
|
|
5
|
+
import {
|
|
6
|
+
AlertDialog,
|
|
7
|
+
AlertDialogAction,
|
|
8
|
+
AlertDialogCancel,
|
|
9
|
+
AlertDialogContent,
|
|
10
|
+
AlertDialogDescription,
|
|
11
|
+
AlertDialogFooter,
|
|
12
|
+
AlertDialogHeader,
|
|
13
|
+
AlertDialogTitle,
|
|
14
|
+
AlertDialogTrigger,
|
|
15
|
+
} from './alert-dialog';
|
|
16
|
+
import { Button } from './button';
|
|
17
|
+
|
|
18
|
+
describe('AlertDialog Component', () => {
|
|
19
|
+
it('renders basic alert dialog', () => {
|
|
20
|
+
cy.mount(
|
|
21
|
+
<AlertDialog>
|
|
22
|
+
<AlertDialogTrigger asChild>
|
|
23
|
+
<Button variant="destructive">Delete Item</Button>
|
|
24
|
+
</AlertDialogTrigger>
|
|
25
|
+
<AlertDialogContent>
|
|
26
|
+
<AlertDialogHeader>
|
|
27
|
+
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
|
|
28
|
+
<AlertDialogDescription>
|
|
29
|
+
This action cannot be undone. This will permanently delete your account and remove
|
|
30
|
+
your data from our servers.
|
|
31
|
+
</AlertDialogDescription>
|
|
32
|
+
</AlertDialogHeader>
|
|
33
|
+
<AlertDialogFooter>
|
|
34
|
+
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
|
35
|
+
<AlertDialogAction>Continue</AlertDialogAction>
|
|
36
|
+
</AlertDialogFooter>
|
|
37
|
+
</AlertDialogContent>
|
|
38
|
+
</AlertDialog>
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
cy.contains('button', 'Delete Item').should('be.visible');
|
|
42
|
+
cy.contains('Are you absolutely sure?').should('not.exist');
|
|
43
|
+
|
|
44
|
+
// Open dialog
|
|
45
|
+
cy.contains('button', 'Delete Item').click();
|
|
46
|
+
cy.contains('Are you absolutely sure?').should('be.visible');
|
|
47
|
+
cy.contains('This action cannot be undone').should('be.visible');
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('handles action button click', () => {
|
|
51
|
+
const onAction = cy.stub();
|
|
52
|
+
|
|
53
|
+
cy.mount(
|
|
54
|
+
<AlertDialog>
|
|
55
|
+
<AlertDialogTrigger>Delete</AlertDialogTrigger>
|
|
56
|
+
<AlertDialogContent>
|
|
57
|
+
<AlertDialogTitle>Confirm Deletion</AlertDialogTitle>
|
|
58
|
+
<AlertDialogDescription>
|
|
59
|
+
Are you sure you want to delete this item?
|
|
60
|
+
</AlertDialogDescription>
|
|
61
|
+
<AlertDialogFooter>
|
|
62
|
+
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
|
63
|
+
<AlertDialogAction onClick={onAction}>Delete</AlertDialogAction>
|
|
64
|
+
</AlertDialogFooter>
|
|
65
|
+
</AlertDialogContent>
|
|
66
|
+
</AlertDialog>
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
// Open the dialog
|
|
70
|
+
cy.contains('Delete').click();
|
|
71
|
+
|
|
72
|
+
// Wait for dialog to appear and ensure it's visible
|
|
73
|
+
cy.contains('Confirm Deletion').should('be.visible');
|
|
74
|
+
|
|
75
|
+
// Try clicking the specific action button using a more precise selector
|
|
76
|
+
cy.get('[role="alertdialog"]').within(() => {
|
|
77
|
+
cy.contains('button', 'Delete').click({ force: true });
|
|
78
|
+
});
|
|
79
|
+
cy.wrap(onAction).should('have.been.called');
|
|
80
|
+
|
|
81
|
+
// Dialog should close
|
|
82
|
+
cy.contains('Confirm Deletion').should('not.exist');
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('handles cancel button click', () => {
|
|
86
|
+
const onCancel = cy.stub();
|
|
87
|
+
|
|
88
|
+
cy.mount(
|
|
89
|
+
<AlertDialog>
|
|
90
|
+
<AlertDialogTrigger>Show Alert</AlertDialogTrigger>
|
|
91
|
+
<AlertDialogContent>
|
|
92
|
+
<AlertDialogTitle>Warning</AlertDialogTitle>
|
|
93
|
+
<AlertDialogDescription>This is a warning message.</AlertDialogDescription>
|
|
94
|
+
<AlertDialogFooter>
|
|
95
|
+
<AlertDialogCancel onClick={onCancel}>Cancel</AlertDialogCancel>
|
|
96
|
+
<AlertDialogAction>OK</AlertDialogAction>
|
|
97
|
+
</AlertDialogFooter>
|
|
98
|
+
</AlertDialogContent>
|
|
99
|
+
</AlertDialog>
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
cy.contains('Show Alert').click();
|
|
103
|
+
cy.contains('button', 'Cancel').click({ force: true });
|
|
104
|
+
cy.wrap(onCancel).should('have.been.called');
|
|
105
|
+
|
|
106
|
+
// Dialog should close
|
|
107
|
+
cy.contains('Warning').should('not.exist');
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('works as controlled component', () => {
|
|
111
|
+
const TestComponent = () => {
|
|
112
|
+
const [open, setOpen] = React.useState(false);
|
|
113
|
+
const [confirmed, setConfirmed] = React.useState(false);
|
|
114
|
+
|
|
115
|
+
return (
|
|
116
|
+
<div>
|
|
117
|
+
<button onClick={() => setOpen(true)}>Open Alert</button>
|
|
118
|
+
<p>Confirmed: {confirmed ? 'Yes' : 'No'}</p>
|
|
119
|
+
|
|
120
|
+
<AlertDialog open={open} onOpenChange={setOpen}>
|
|
121
|
+
<AlertDialogContent>
|
|
122
|
+
<AlertDialogTitle>Confirm Action</AlertDialogTitle>
|
|
123
|
+
<AlertDialogDescription>Do you want to proceed?</AlertDialogDescription>
|
|
124
|
+
<AlertDialogFooter>
|
|
125
|
+
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
|
126
|
+
<AlertDialogAction
|
|
127
|
+
onClick={() => {
|
|
128
|
+
setConfirmed(true);
|
|
129
|
+
setOpen(false);
|
|
130
|
+
}}
|
|
131
|
+
>
|
|
132
|
+
Confirm
|
|
133
|
+
</AlertDialogAction>
|
|
134
|
+
</AlertDialogFooter>
|
|
135
|
+
</AlertDialogContent>
|
|
136
|
+
</AlertDialog>
|
|
137
|
+
</div>
|
|
138
|
+
);
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
cy.mount(<TestComponent />);
|
|
142
|
+
|
|
143
|
+
cy.contains('Confirmed: No').should('be.visible');
|
|
144
|
+
cy.contains('Open Alert').click();
|
|
145
|
+
cy.contains('Confirm Action').should('be.visible');
|
|
146
|
+
|
|
147
|
+
cy.contains('button', 'Confirm').click({ force: true });
|
|
148
|
+
cy.contains('Confirmed: Yes').should('be.visible');
|
|
149
|
+
cy.contains('Confirm Action').should('not.exist');
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('prevents closing on escape by default', () => {
|
|
153
|
+
cy.mount(
|
|
154
|
+
<AlertDialog>
|
|
155
|
+
<AlertDialogTrigger>Important Action</AlertDialogTrigger>
|
|
156
|
+
<AlertDialogContent>
|
|
157
|
+
<AlertDialogTitle>Critical Decision</AlertDialogTitle>
|
|
158
|
+
<AlertDialogDescription>This requires your explicit action.</AlertDialogDescription>
|
|
159
|
+
<AlertDialogFooter>
|
|
160
|
+
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
|
161
|
+
<AlertDialogAction>Proceed</AlertDialogAction>
|
|
162
|
+
</AlertDialogFooter>
|
|
163
|
+
</AlertDialogContent>
|
|
164
|
+
</AlertDialog>
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
cy.contains('Important Action').click();
|
|
168
|
+
cy.contains('Critical Decision').should('be.visible');
|
|
169
|
+
|
|
170
|
+
// Try to close with ESC
|
|
171
|
+
cy.get('body').type('{esc}');
|
|
172
|
+
cy.contains('Critical Decision').should('be.visible');
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('supports custom className', () => {
|
|
176
|
+
cy.mount(
|
|
177
|
+
<AlertDialog>
|
|
178
|
+
<AlertDialogTrigger className="custom-trigger">Open</AlertDialogTrigger>
|
|
179
|
+
<AlertDialogContent className="bg-red-50 custom-content">
|
|
180
|
+
<AlertDialogTitle className="text-red-900">Error</AlertDialogTitle>
|
|
181
|
+
<AlertDialogDescription className="text-red-700">
|
|
182
|
+
Something went wrong!
|
|
183
|
+
</AlertDialogDescription>
|
|
184
|
+
<AlertDialogFooter>
|
|
185
|
+
<AlertDialogAction className="bg-red-600">OK</AlertDialogAction>
|
|
186
|
+
</AlertDialogFooter>
|
|
187
|
+
</AlertDialogContent>
|
|
188
|
+
</AlertDialog>
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
cy.get('.custom-trigger').should('exist');
|
|
192
|
+
cy.contains('Open').click();
|
|
193
|
+
cy.get('.custom-content').should('have.class', 'bg-red-50');
|
|
194
|
+
cy.get('.text-red-900').should('contain', 'Error');
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('handles async operations', () => {
|
|
198
|
+
const TestComponent = () => {
|
|
199
|
+
const [loading, setLoading] = React.useState(false);
|
|
200
|
+
const [open, setOpen] = React.useState(false);
|
|
201
|
+
|
|
202
|
+
const handleDelete = async () => {
|
|
203
|
+
setLoading(true);
|
|
204
|
+
// Simulate async operation
|
|
205
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
206
|
+
setLoading(false);
|
|
207
|
+
setOpen(false);
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
return (
|
|
211
|
+
<AlertDialog open={open} onOpenChange={setOpen}>
|
|
212
|
+
<AlertDialogTrigger asChild>
|
|
213
|
+
<Button>Delete</Button>
|
|
214
|
+
</AlertDialogTrigger>
|
|
215
|
+
<AlertDialogContent>
|
|
216
|
+
<AlertDialogTitle>Delete Item</AlertDialogTitle>
|
|
217
|
+
<AlertDialogDescription>
|
|
218
|
+
Are you sure you want to delete this item?
|
|
219
|
+
</AlertDialogDescription>
|
|
220
|
+
<AlertDialogFooter>
|
|
221
|
+
<AlertDialogCancel disabled={loading}>Cancel</AlertDialogCancel>
|
|
222
|
+
<AlertDialogAction onClick={handleDelete} disabled={loading}>
|
|
223
|
+
{loading ? 'Deleting...' : 'Delete'}
|
|
224
|
+
</AlertDialogAction>
|
|
225
|
+
</AlertDialogFooter>
|
|
226
|
+
</AlertDialogContent>
|
|
227
|
+
</AlertDialog>
|
|
228
|
+
);
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
cy.mount(<TestComponent />);
|
|
232
|
+
|
|
233
|
+
// Open the dialog
|
|
234
|
+
cy.contains('button', 'Delete').click();
|
|
235
|
+
|
|
236
|
+
// Wait for dialog content to appear
|
|
237
|
+
cy.contains('Delete Item').should('be.visible');
|
|
238
|
+
|
|
239
|
+
// Click the action button within the dialog
|
|
240
|
+
cy.get('[role="alertdialog"]').within(() => {
|
|
241
|
+
cy.contains('button', 'Delete').click({ force: true });
|
|
242
|
+
});
|
|
243
|
+
cy.contains('Deleting...').should('be.visible');
|
|
244
|
+
cy.wait(150);
|
|
245
|
+
cy.contains('Delete Item').should('not.exist');
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it('supports warning variant styling', () => {
|
|
249
|
+
cy.mount(
|
|
250
|
+
<AlertDialog>
|
|
251
|
+
<AlertDialogTrigger>
|
|
252
|
+
<Button variant="outline">Show Warning</Button>
|
|
253
|
+
</AlertDialogTrigger>
|
|
254
|
+
<AlertDialogContent>
|
|
255
|
+
<AlertDialogHeader>
|
|
256
|
+
<AlertDialogTitle>⚠️ Warning</AlertDialogTitle>
|
|
257
|
+
<AlertDialogDescription>
|
|
258
|
+
This action may have unintended consequences.
|
|
259
|
+
</AlertDialogDescription>
|
|
260
|
+
</AlertDialogHeader>
|
|
261
|
+
<AlertDialogFooter>
|
|
262
|
+
<AlertDialogCancel>Go Back</AlertDialogCancel>
|
|
263
|
+
<AlertDialogAction className="bg-yellow-600 hover:bg-yellow-700">
|
|
264
|
+
I Understand
|
|
265
|
+
</AlertDialogAction>
|
|
266
|
+
</AlertDialogFooter>
|
|
267
|
+
</AlertDialogContent>
|
|
268
|
+
</AlertDialog>
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
cy.contains('Show Warning').click();
|
|
272
|
+
cy.contains('⚠️ Warning').should('be.visible');
|
|
273
|
+
cy.get('.bg-yellow-600').should('contain', 'I Understand');
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it('handles multiple dialogs', () => {
|
|
277
|
+
cy.mount(
|
|
278
|
+
<div className="space-x-4">
|
|
279
|
+
<AlertDialog>
|
|
280
|
+
<AlertDialogTrigger>First Dialog</AlertDialogTrigger>
|
|
281
|
+
<AlertDialogContent>
|
|
282
|
+
<AlertDialogTitle>First Alert</AlertDialogTitle>
|
|
283
|
+
<AlertDialogFooter>
|
|
284
|
+
<AlertDialogAction>OK</AlertDialogAction>
|
|
285
|
+
</AlertDialogFooter>
|
|
286
|
+
</AlertDialogContent>
|
|
287
|
+
</AlertDialog>
|
|
288
|
+
|
|
289
|
+
<AlertDialog>
|
|
290
|
+
<AlertDialogTrigger>Second Dialog</AlertDialogTrigger>
|
|
291
|
+
<AlertDialogContent>
|
|
292
|
+
<AlertDialogTitle>Second Alert</AlertDialogTitle>
|
|
293
|
+
<AlertDialogFooter>
|
|
294
|
+
<AlertDialogAction>OK</AlertDialogAction>
|
|
295
|
+
</AlertDialogFooter>
|
|
296
|
+
</AlertDialogContent>
|
|
297
|
+
</AlertDialog>
|
|
298
|
+
</div>
|
|
299
|
+
);
|
|
300
|
+
|
|
301
|
+
// Open first dialog
|
|
302
|
+
cy.contains('First Dialog').click();
|
|
303
|
+
cy.contains('First Alert').should('be.visible');
|
|
304
|
+
cy.contains('button', 'OK').click();
|
|
305
|
+
|
|
306
|
+
// Open second dialog
|
|
307
|
+
cy.contains('Second Dialog').click();
|
|
308
|
+
cy.contains('Second Alert').should('be.visible');
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
it('maintains focus management', () => {
|
|
312
|
+
cy.mount(
|
|
313
|
+
<AlertDialog>
|
|
314
|
+
<AlertDialogTrigger>Open Alert</AlertDialogTrigger>
|
|
315
|
+
<AlertDialogContent>
|
|
316
|
+
<AlertDialogTitle>Focus Test</AlertDialogTitle>
|
|
317
|
+
<AlertDialogDescription>
|
|
318
|
+
Focus should be trapped within the dialog.
|
|
319
|
+
</AlertDialogDescription>
|
|
320
|
+
<AlertDialogFooter>
|
|
321
|
+
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
|
322
|
+
<AlertDialogAction>Confirm</AlertDialogAction>
|
|
323
|
+
</AlertDialogFooter>
|
|
324
|
+
</AlertDialogContent>
|
|
325
|
+
</AlertDialog>
|
|
326
|
+
);
|
|
327
|
+
|
|
328
|
+
cy.contains('Open Alert').click();
|
|
329
|
+
|
|
330
|
+
// Focus should be within dialog
|
|
331
|
+
cy.focused().should('exist');
|
|
332
|
+
});
|
|
333
|
+
});
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog';
|
|
3
|
+
|
|
4
|
+
import { cn } from '@/lib/utils';
|
|
5
|
+
import { buttonVariants } from '@/components/ui/button';
|
|
6
|
+
|
|
7
|
+
const AlertDialog = AlertDialogPrimitive.Root;
|
|
8
|
+
|
|
9
|
+
const AlertDialogTrigger = AlertDialogPrimitive.Trigger;
|
|
10
|
+
|
|
11
|
+
const AlertDialogPortal = AlertDialogPrimitive.Portal;
|
|
12
|
+
|
|
13
|
+
const AlertDialogOverlay = React.forwardRef<
|
|
14
|
+
React.ElementRef<typeof AlertDialogPrimitive.Overlay>,
|
|
15
|
+
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>
|
|
16
|
+
>(({ className, ...props }, ref) => (
|
|
17
|
+
<AlertDialogPrimitive.Overlay
|
|
18
|
+
className={cn(
|
|
19
|
+
'fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
|
|
20
|
+
className
|
|
21
|
+
)}
|
|
22
|
+
{...props}
|
|
23
|
+
ref={ref}
|
|
24
|
+
/>
|
|
25
|
+
));
|
|
26
|
+
AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName;
|
|
27
|
+
|
|
28
|
+
const AlertDialogContent = React.forwardRef<
|
|
29
|
+
React.ElementRef<typeof AlertDialogPrimitive.Content>,
|
|
30
|
+
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content>
|
|
31
|
+
>(({ className, ...props }, ref) => (
|
|
32
|
+
<AlertDialogPortal>
|
|
33
|
+
<AlertDialogOverlay />
|
|
34
|
+
<AlertDialogPrimitive.Content
|
|
35
|
+
ref={ref}
|
|
36
|
+
className={cn(
|
|
37
|
+
'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg',
|
|
38
|
+
className
|
|
39
|
+
)}
|
|
40
|
+
{...props}
|
|
41
|
+
/>
|
|
42
|
+
</AlertDialogPortal>
|
|
43
|
+
));
|
|
44
|
+
AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName;
|
|
45
|
+
|
|
46
|
+
const AlertDialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
|
47
|
+
<div className={cn('flex flex-col space-y-2 text-center sm:text-left', className)} {...props} />
|
|
48
|
+
);
|
|
49
|
+
AlertDialogHeader.displayName = 'AlertDialogHeader';
|
|
50
|
+
|
|
51
|
+
const AlertDialogFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
|
52
|
+
<div
|
|
53
|
+
className={cn('flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2', className)}
|
|
54
|
+
{...props}
|
|
55
|
+
/>
|
|
56
|
+
);
|
|
57
|
+
AlertDialogFooter.displayName = 'AlertDialogFooter';
|
|
58
|
+
|
|
59
|
+
const AlertDialogTitle = React.forwardRef<
|
|
60
|
+
React.ElementRef<typeof AlertDialogPrimitive.Title>,
|
|
61
|
+
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>
|
|
62
|
+
>(({ className, ...props }, ref) => (
|
|
63
|
+
<AlertDialogPrimitive.Title
|
|
64
|
+
ref={ref}
|
|
65
|
+
className={cn('text-lg font-semibold', className)}
|
|
66
|
+
{...props}
|
|
67
|
+
/>
|
|
68
|
+
));
|
|
69
|
+
AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName;
|
|
70
|
+
|
|
71
|
+
const AlertDialogDescription = React.forwardRef<
|
|
72
|
+
React.ElementRef<typeof AlertDialogPrimitive.Description>,
|
|
73
|
+
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>
|
|
74
|
+
>(({ className, ...props }, ref) => (
|
|
75
|
+
<AlertDialogPrimitive.Description
|
|
76
|
+
ref={ref}
|
|
77
|
+
className={cn('text-sm text-muted-foreground', className)}
|
|
78
|
+
{...props}
|
|
79
|
+
/>
|
|
80
|
+
));
|
|
81
|
+
AlertDialogDescription.displayName = AlertDialogPrimitive.Description.displayName;
|
|
82
|
+
|
|
83
|
+
const AlertDialogAction = React.forwardRef<
|
|
84
|
+
React.ElementRef<typeof AlertDialogPrimitive.Action>,
|
|
85
|
+
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>
|
|
86
|
+
>(({ className, ...props }, ref) => (
|
|
87
|
+
<AlertDialogPrimitive.Action ref={ref} className={cn(buttonVariants(), className)} {...props} />
|
|
88
|
+
));
|
|
89
|
+
AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName;
|
|
90
|
+
|
|
91
|
+
const AlertDialogCancel = React.forwardRef<
|
|
92
|
+
React.ElementRef<typeof AlertDialogPrimitive.Cancel>,
|
|
93
|
+
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>
|
|
94
|
+
>(({ className, ...props }, ref) => (
|
|
95
|
+
<AlertDialogPrimitive.Cancel
|
|
96
|
+
ref={ref}
|
|
97
|
+
className={cn(buttonVariants({ variant: 'outline' }), 'mt-2 sm:mt-0', className)}
|
|
98
|
+
{...props}
|
|
99
|
+
/>
|
|
100
|
+
));
|
|
101
|
+
AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName;
|
|
102
|
+
|
|
103
|
+
export {
|
|
104
|
+
AlertDialog,
|
|
105
|
+
AlertDialogPortal,
|
|
106
|
+
AlertDialogOverlay,
|
|
107
|
+
AlertDialogTrigger,
|
|
108
|
+
AlertDialogContent,
|
|
109
|
+
AlertDialogHeader,
|
|
110
|
+
AlertDialogFooter,
|
|
111
|
+
AlertDialogTitle,
|
|
112
|
+
AlertDialogDescription,
|
|
113
|
+
AlertDialogAction,
|
|
114
|
+
AlertDialogCancel,
|
|
115
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { cva, type VariantProps } from 'class-variance-authority';
|
|
3
|
+
|
|
4
|
+
import { cn } from '@/lib/utils';
|
|
5
|
+
|
|
6
|
+
const alertVariants = cva(
|
|
7
|
+
'relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground',
|
|
8
|
+
{
|
|
9
|
+
variants: {
|
|
10
|
+
variant: {
|
|
11
|
+
default: 'bg-background text-foreground',
|
|
12
|
+
destructive:
|
|
13
|
+
'border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive',
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
defaultVariants: {
|
|
17
|
+
variant: 'default',
|
|
18
|
+
},
|
|
19
|
+
}
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
const Alert = React.forwardRef<
|
|
23
|
+
HTMLDivElement,
|
|
24
|
+
React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
|
|
25
|
+
>(({ className, variant, ...props }, ref) => (
|
|
26
|
+
<div ref={ref} role="alert" className={cn(alertVariants({ variant }), className)} {...props} />
|
|
27
|
+
));
|
|
28
|
+
Alert.displayName = 'Alert';
|
|
29
|
+
|
|
30
|
+
const AlertTitle = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLHeadingElement>>(
|
|
31
|
+
({ className, ...props }, ref) => (
|
|
32
|
+
<h5
|
|
33
|
+
ref={ref}
|
|
34
|
+
className={cn('mb-1 font-medium leading-none tracking-tight', className)}
|
|
35
|
+
{...props}
|
|
36
|
+
/>
|
|
37
|
+
)
|
|
38
|
+
);
|
|
39
|
+
AlertTitle.displayName = 'AlertTitle';
|
|
40
|
+
|
|
41
|
+
const AlertDescription = React.forwardRef<
|
|
42
|
+
HTMLParagraphElement,
|
|
43
|
+
React.HTMLAttributes<HTMLParagraphElement>
|
|
44
|
+
>(({ className, ...props }, ref) => (
|
|
45
|
+
<div ref={ref} className={cn('text-sm [&_p]:leading-relaxed', className)} {...props} />
|
|
46
|
+
));
|
|
47
|
+
AlertDescription.displayName = 'AlertDescription';
|
|
48
|
+
|
|
49
|
+
export { Alert, AlertTitle, AlertDescription };
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/// <reference types="cypress" />
|
|
2
|
+
/// <reference path="../../../cypress/support/types.d.ts" />
|
|
3
|
+
|
|
4
|
+
import React from 'react';
|
|
5
|
+
import { Avatar, AvatarFallback, AvatarImage } from './avatar';
|
|
6
|
+
|
|
7
|
+
describe('Avatar Component', () => {
|
|
8
|
+
it('renders image correctly', () => {
|
|
9
|
+
cy.mount(
|
|
10
|
+
<Avatar>
|
|
11
|
+
<AvatarImage src="https://github.com/shadcn.png" alt="@shadcn" />
|
|
12
|
+
<AvatarFallback>CN</AvatarFallback>
|
|
13
|
+
</Avatar>
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
cy.get('[data-testid="avatar"]').should('exist');
|
|
17
|
+
cy.get('[data-testid="avatar-image"]').should(
|
|
18
|
+
'have.attr',
|
|
19
|
+
'src',
|
|
20
|
+
'https://github.com/shadcn.png'
|
|
21
|
+
);
|
|
22
|
+
cy.get('[data-testid="avatar-image"]').should('have.attr', 'alt', '@shadcn');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('shows fallback when image fails to load', () => {
|
|
26
|
+
cy.mount(
|
|
27
|
+
<Avatar>
|
|
28
|
+
<AvatarImage src="https://broken-image-url.com/404.png" alt="broken" />
|
|
29
|
+
<AvatarFallback>FB</AvatarFallback>
|
|
30
|
+
</Avatar>
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
// Wait for image to fail loading
|
|
34
|
+
cy.wait(500);
|
|
35
|
+
cy.get('[data-testid="avatar-fallback"]').should('be.visible');
|
|
36
|
+
cy.get('[data-testid="avatar-fallback"]').should('contain', 'FB');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('renders fallback only when no image provided', () => {
|
|
40
|
+
cy.mount(
|
|
41
|
+
<Avatar>
|
|
42
|
+
<AvatarFallback>JD</AvatarFallback>
|
|
43
|
+
</Avatar>
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
cy.get('[data-testid="avatar-fallback"]').should('be.visible');
|
|
47
|
+
cy.get('[data-testid="avatar-fallback"]').should('contain', 'JD');
|
|
48
|
+
cy.get('[data-testid="avatar-image"]').should('not.exist');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('applies custom className to avatar', () => {
|
|
52
|
+
cy.mount(
|
|
53
|
+
<Avatar className="w-20 h-20">
|
|
54
|
+
<AvatarFallback>LG</AvatarFallback>
|
|
55
|
+
</Avatar>
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
cy.get('[data-testid="avatar"]').should('have.class', 'w-20').should('have.class', 'h-20');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('renders multiple avatars in a group', () => {
|
|
62
|
+
cy.mount(
|
|
63
|
+
<div className="flex -space-x-4">
|
|
64
|
+
<Avatar>
|
|
65
|
+
<AvatarImage src="https://github.com/user1.png" />
|
|
66
|
+
<AvatarFallback>U1</AvatarFallback>
|
|
67
|
+
</Avatar>
|
|
68
|
+
<Avatar>
|
|
69
|
+
<AvatarImage src="https://github.com/user2.png" />
|
|
70
|
+
<AvatarFallback>U2</AvatarFallback>
|
|
71
|
+
</Avatar>
|
|
72
|
+
<Avatar>
|
|
73
|
+
<AvatarImage src="https://github.com/user3.png" />
|
|
74
|
+
<AvatarFallback>U3</AvatarFallback>
|
|
75
|
+
</Avatar>
|
|
76
|
+
</div>
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
cy.get('[data-testid="avatar"]').should('have.length', 3);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('supports different sizes via className', () => {
|
|
83
|
+
cy.mount(
|
|
84
|
+
<div className="flex gap-4">
|
|
85
|
+
<Avatar className="h-8 w-8">
|
|
86
|
+
<AvatarFallback>SM</AvatarFallback>
|
|
87
|
+
</Avatar>
|
|
88
|
+
<Avatar className="h-12 w-12">
|
|
89
|
+
<AvatarFallback>MD</AvatarFallback>
|
|
90
|
+
</Avatar>
|
|
91
|
+
<Avatar className="h-16 w-16">
|
|
92
|
+
<AvatarFallback>LG</AvatarFallback>
|
|
93
|
+
</Avatar>
|
|
94
|
+
</div>
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
cy.get('[data-testid="avatar"]').first().should('have.class', 'h-8');
|
|
98
|
+
cy.get('[data-testid="avatar"]').eq(1).should('have.class', 'h-12');
|
|
99
|
+
cy.get('[data-testid="avatar"]').last().should('have.class', 'h-16');
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('displays initials correctly in fallback', () => {
|
|
103
|
+
cy.mount(
|
|
104
|
+
<Avatar>
|
|
105
|
+
<AvatarFallback>John Doe</AvatarFallback>
|
|
106
|
+
</Avatar>
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
// Note: The component should ideally show "JD" but shows full text
|
|
110
|
+
cy.get('[data-testid="avatar-fallback"]').should('contain', 'John Doe');
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('supports custom fallback styles', () => {
|
|
114
|
+
cy.mount(
|
|
115
|
+
<Avatar>
|
|
116
|
+
<AvatarFallback className="bg-blue-500 text-white">AB</AvatarFallback>
|
|
117
|
+
</Avatar>
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
cy.get('[data-testid="avatar-fallback"]')
|
|
121
|
+
.should('have.class', 'bg-blue-500')
|
|
122
|
+
.should('have.class', 'text-white');
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('maintains aspect ratio', () => {
|
|
126
|
+
cy.mount(
|
|
127
|
+
<Avatar>
|
|
128
|
+
<AvatarImage src="https://github.com/shadcn.png" />
|
|
129
|
+
<AvatarFallback>CN</AvatarFallback>
|
|
130
|
+
</Avatar>
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
cy.get('[data-testid="avatar-image"]').should('have.class', 'aspect-square');
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('handles click events', () => {
|
|
137
|
+
const onClick = cy.stub();
|
|
138
|
+
|
|
139
|
+
cy.mount(
|
|
140
|
+
<Avatar onClick={onClick} className="cursor-pointer">
|
|
141
|
+
<AvatarFallback>CL</AvatarFallback>
|
|
142
|
+
</Avatar>
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
cy.get('[data-testid="avatar"]').click();
|
|
146
|
+
cy.wrap(onClick).should('have.been.calledOnce');
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('works with status indicators', () => {
|
|
150
|
+
cy.mount(
|
|
151
|
+
<div className="relative inline-flex">
|
|
152
|
+
<Avatar>
|
|
153
|
+
<AvatarFallback>ON</AvatarFallback>
|
|
154
|
+
</Avatar>
|
|
155
|
+
<span
|
|
156
|
+
className="absolute -bottom-1 -right-1 h-3.5 w-3.5 rounded-full bg-green-500 ring-2 ring-white"
|
|
157
|
+
data-testid="status-indicator"
|
|
158
|
+
/>
|
|
159
|
+
</div>
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
cy.get('[data-testid="avatar"]').should('exist');
|
|
163
|
+
cy.get('[data-testid="status-indicator"]')
|
|
164
|
+
.should('exist')
|
|
165
|
+
.should('have.class', 'bg-green-500')
|
|
166
|
+
.should('have.class', 'ring-2');
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('supports data attributes', () => {
|
|
170
|
+
cy.mount(
|
|
171
|
+
<Avatar data-testid="custom-avatar" data-user-id="123">
|
|
172
|
+
<AvatarFallback>DA</AvatarFallback>
|
|
173
|
+
</Avatar>
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
cy.get('[data-testid="custom-avatar"]')
|
|
177
|
+
.should('exist')
|
|
178
|
+
.should('have.attr', 'data-user-id', '123');
|
|
179
|
+
});
|
|
180
|
+
});
|