@slyxup/ui 0.1.0 → 0.2.1
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 +4 -0
- package/CHANGELOG.md +27 -0
- package/LICENSE +21 -0
- package/README.md +105 -0
- package/dist/components/BillingPortal/BillingPortal.d.ts +22 -0
- package/dist/components/BillingPortal/BillingPortal.d.ts.map +1 -0
- package/dist/components/BillingPortal/BillingPortal.js +59 -0
- package/dist/components/BillingPortal/BillingPortal.js.map +1 -0
- package/dist/components/EmailVerification/EmailVerification.d.ts +9 -0
- package/dist/components/EmailVerification/EmailVerification.d.ts.map +1 -0
- package/dist/components/EmailVerification/EmailVerification.js +72 -0
- package/dist/components/EmailVerification/EmailVerification.js.map +1 -0
- package/dist/components/ForgotPassword/ForgotPassword.d.ts +8 -0
- package/dist/components/ForgotPassword/ForgotPassword.d.ts.map +1 -0
- package/dist/components/ForgotPassword/ForgotPassword.js +43 -0
- package/dist/components/ForgotPassword/ForgotPassword.js.map +1 -0
- package/dist/components/PricingTable/PricingTable.d.ts +19 -0
- package/dist/components/PricingTable/PricingTable.d.ts.map +1 -0
- package/dist/components/PricingTable/PricingTable.js +52 -0
- package/dist/components/PricingTable/PricingTable.js.map +1 -0
- package/dist/components/ResetPassword/ResetPassword.d.ts +9 -0
- package/dist/components/ResetPassword/ResetPassword.d.ts.map +1 -0
- package/dist/components/ResetPassword/ResetPassword.js +50 -0
- package/dist/components/ResetPassword/ResetPassword.js.map +1 -0
- package/dist/components/SignIn/SignIn.d.ts +11 -0
- package/dist/components/SignIn/SignIn.d.ts.map +1 -0
- package/dist/components/SignIn/SignIn.js +53 -0
- package/dist/components/SignIn/SignIn.js.map +1 -0
- package/dist/components/SignUp/SignUp.d.ts +8 -0
- package/dist/components/SignUp/SignUp.d.ts.map +1 -0
- package/dist/components/SignUp/SignUp.js +54 -0
- package/dist/components/SignUp/SignUp.js.map +1 -0
- package/dist/components/SocialButtons/SocialButtons.d.ts +9 -0
- package/dist/components/SocialButtons/SocialButtons.d.ts.map +1 -0
- package/dist/components/SocialButtons/SocialButtons.js +16 -0
- package/dist/components/SocialButtons/SocialButtons.js.map +1 -0
- package/dist/components/UserButton/UserButton.d.ts +3 -0
- package/dist/components/UserButton/UserButton.d.ts.map +1 -0
- package/dist/components/UserButton/UserButton.js +43 -0
- package/dist/components/UserButton/UserButton.js.map +1 -0
- package/dist/components/UserProfile/UserProfile.d.ts +3 -0
- package/dist/components/UserProfile/UserProfile.d.ts.map +1 -0
- package/dist/components/UserProfile/UserProfile.js +38 -0
- package/dist/components/UserProfile/UserProfile.js.map +1 -0
- package/dist/icons.d.ts +6 -0
- package/dist/icons.d.ts.map +1 -0
- package/dist/icons.js +15 -0
- package/dist/icons.js.map +1 -0
- package/dist/index.d.ts +15 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +22 -1
- package/dist/index.js.map +1 -1
- package/dist/styles.d.ts +9 -0
- package/dist/styles.d.ts.map +1 -0
- package/dist/styles.js +219 -0
- package/dist/styles.js.map +1 -0
- package/package.json +35 -3
- package/src/components/BillingPortal/BillingPortal.tsx +182 -0
- package/src/components/EmailVerification/EmailVerification.tsx +180 -0
- package/src/components/ForgotPassword/ForgotPassword.tsx +122 -0
- package/src/components/PricingTable/PricingTable.tsx +155 -0
- package/src/components/ResetPassword/ResetPassword.tsx +120 -0
- package/src/components/SignIn/SignIn.tsx +168 -0
- package/src/components/SignUp/SignUp.tsx +181 -0
- package/src/components/SocialButtons/SocialButtons.tsx +39 -0
- package/src/components/UserButton/UserButton.tsx +89 -0
- package/src/components/UserProfile/UserProfile.tsx +93 -0
- package/src/icons.tsx +71 -0
- package/src/index.ts +37 -1
- package/src/styles.ts +221 -0
- package/test/components.test.tsx +135 -0
- package/test/icons.test.tsx +32 -0
- package/test/styles.test.ts +47 -0
- package/vitest.config.ts +2 -0
package/src/styles.ts
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SlyxUp UI — design tokens + stylesheet.
|
|
3
|
+
* Injected once via <SlyxUpStyles />. Theme with CSS variables on :root or .slyxup-scope.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export const FONT_LINK = `<link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><link href="https://fonts.googleapis.com/css2?family=DM+Sans:opsz,wght@9..40,300;9..40,400;9..40,500;9..40,600;9..40,700;9..40,800&family=Space+Grotesk:wght@400;500;600;700&display=swap" rel="stylesheet">`;
|
|
7
|
+
|
|
8
|
+
export const CSS = `
|
|
9
|
+
@import url('https://fonts.googleapis.com/css2?family=DM+Sans:opsz,wght@9..40,300;9..40,400;9..40,500;9..40,600;9..40,700;9..40,800&family=Space+Grotesk:wght@400;500;600;700&display=swap');
|
|
10
|
+
|
|
11
|
+
.slyxup-root {
|
|
12
|
+
--slx-accent: #5b5bd6;
|
|
13
|
+
--slx-accent-hover: #4c4cc4;
|
|
14
|
+
--slx-accent-soft: rgba(91, 91, 214, 0.14);
|
|
15
|
+
--slx-bg: #ffffff;
|
|
16
|
+
--slx-bg-page: #e9eaf6;
|
|
17
|
+
--slx-ink: #16161d;
|
|
18
|
+
--slx-muted: #6f6f7b;
|
|
19
|
+
--slx-border: #d8d8e8;
|
|
20
|
+
--slx-danger: #d64550;
|
|
21
|
+
--slx-radius: 12px;
|
|
22
|
+
--slx-font: "DM Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
23
|
+
--slx-display: "Space Grotesk", "DM Sans", sans-serif;
|
|
24
|
+
--slx-mono: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
25
|
+
|
|
26
|
+
font-family: var(--slx-font);
|
|
27
|
+
color: var(--slx-ink);
|
|
28
|
+
-webkit-font-smoothing: antialiased;
|
|
29
|
+
}
|
|
30
|
+
@media (prefers-color-scheme: dark) {
|
|
31
|
+
.slyxup-root:not(.slyxup-light) {
|
|
32
|
+
--slx-accent: #8484f2;
|
|
33
|
+
--slx-accent-hover: #9696f5;
|
|
34
|
+
--slx-accent-soft: rgba(132, 132, 242, 0.14);
|
|
35
|
+
--slx-bg: #191920;
|
|
36
|
+
--slx-bg-page: #121218;
|
|
37
|
+
--slx-ink: #f2f2f5;
|
|
38
|
+
--slx-muted: #9a9aa6;
|
|
39
|
+
--slx-border: #2c2c36;
|
|
40
|
+
--slx-danger: #f0737d;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
@keyframes slx-shake {
|
|
45
|
+
10%, 90% { transform: translateX(-1px); }
|
|
46
|
+
20%, 80% { transform: translateX(2px); }
|
|
47
|
+
30%, 50%, 70% { transform: translateX(-4px); }
|
|
48
|
+
40%, 60% { transform: translateX(4px); }
|
|
49
|
+
}
|
|
50
|
+
@keyframes slx-spin { to { transform: rotate(360deg); } }
|
|
51
|
+
@media (prefers-reduced-motion: reduce) {
|
|
52
|
+
.slyxup-root * { animation: none !important; transition: none !important; }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/* ── Card ── */
|
|
56
|
+
.slx-card {
|
|
57
|
+
width: 100%;
|
|
58
|
+
max-width: 380px;
|
|
59
|
+
background: var(--slx-bg);
|
|
60
|
+
border: 1px solid var(--slx-border);
|
|
61
|
+
border-radius: calc(var(--slx-radius) + 4px);
|
|
62
|
+
padding: 32px;
|
|
63
|
+
box-shadow: 0 4px 12px rgba(16,16,29,.08), 0 16px 40px rgba(16,16,29,.12), 0 0 0 1px rgba(16,16,29,.04);
|
|
64
|
+
box-sizing: border-box;
|
|
65
|
+
}
|
|
66
|
+
.slyxup-root .slx-card { background: #ffffff; }
|
|
67
|
+
.slx-card-error { animation: slx-shake .45s cubic-bezier(.36,.07,.19,.97) both; }
|
|
68
|
+
|
|
69
|
+
/* ── Header / keyhole mark ── */
|
|
70
|
+
.slx-mark {
|
|
71
|
+
width: 44px; height: 44px; border-radius: 12px;
|
|
72
|
+
display: flex; align-items: center; justify-content: center;
|
|
73
|
+
background: linear-gradient(135deg, var(--slx-accent), #9a6cf0);
|
|
74
|
+
margin-bottom: 18px;
|
|
75
|
+
}
|
|
76
|
+
.slx-mark svg { display: block; }
|
|
77
|
+
.slx-title { font-family: var(--slx-display); font-size: 20px; font-weight: 650; letter-spacing: -0.02em; margin: 0 0 6px; }
|
|
78
|
+
.slx-subtitle { font-size: 13.5px; color: var(--slx-muted); margin: 0 0 22px; line-height: 1.5; }
|
|
79
|
+
|
|
80
|
+
/* ── Fields ── */
|
|
81
|
+
.slx-field { margin-bottom: 14px; }
|
|
82
|
+
.slx-label { display: block; font-size: 12.5px; font-weight: 550; margin-bottom: 6px; letter-spacing: 0.01em; }
|
|
83
|
+
.slx-input {
|
|
84
|
+
width: 100%; box-sizing: border-box;
|
|
85
|
+
font: inherit; font-size: 14px; color: var(--slx-ink);
|
|
86
|
+
background: transparent;
|
|
87
|
+
border: 1px solid var(--slx-border);
|
|
88
|
+
border-radius: var(--slx-radius);
|
|
89
|
+
padding: 10px 12px;
|
|
90
|
+
outline: none;
|
|
91
|
+
transition: border-color .15s, box-shadow .15s;
|
|
92
|
+
}
|
|
93
|
+
.slx-input::placeholder { color: var(--slx-muted); opacity: .75; }
|
|
94
|
+
.slx-input:focus-visible {
|
|
95
|
+
border-color: var(--slx-accent);
|
|
96
|
+
box-shadow: 0 0 0 3px var(--slx-accent-soft);
|
|
97
|
+
}
|
|
98
|
+
.slx-hint { font-size: 12px; color: var(--slx-muted); margin-top: 5px; }
|
|
99
|
+
.slx-error-text {
|
|
100
|
+
font-size: 13px; color: var(--slx-danger);
|
|
101
|
+
background: color-mix(in srgb, var(--slx-danger) 9%, transparent);
|
|
102
|
+
border-radius: 8px; padding: 9px 11px; margin: 0 0 14px; line-height: 1.4;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/* ── Button ── */
|
|
106
|
+
.slx-btn {
|
|
107
|
+
width: 100%; box-sizing: border-box;
|
|
108
|
+
font-family: var(--slx-font); font-size: 14px; font-weight: 600; letter-spacing: 0.01em;
|
|
109
|
+
color: #fff; background: #0a0a0f;
|
|
110
|
+
border: 1px solid #0a0a0f; border-radius: var(--slx-radius);
|
|
111
|
+
padding: 11px 14px; cursor: pointer;
|
|
112
|
+
display: inline-flex; align-items: center; justify-content: center; gap: 8px;
|
|
113
|
+
transition: background .15s, transform .06s, border-color .15s;
|
|
114
|
+
}
|
|
115
|
+
.slx-btn:hover { background: #1a1a23; border-color: #1a1a23; }
|
|
116
|
+
.slx-btn:active { transform: scale(.985); background: #000; }
|
|
117
|
+
.slx-btn:focus-visible { outline: none; box-shadow: 0 0 0 3px rgba(10,10,15,.14), 0 0 0 1px #0a0a0f; }
|
|
118
|
+
.slx-btn[disabled] { opacity: .6; cursor: not-allowed; }
|
|
119
|
+
@media (prefers-color-scheme: dark) {
|
|
120
|
+
.slyxup-root:not(.slyxup-light) .slx-btn { background: #f2f2f5; color: #0a0a0f; border-color: #f2f2f5; }
|
|
121
|
+
.slyxup-root:not(.slyxup-light) .slx-btn:hover { background: #e6e6eb; border-color: #e6e6eb; }
|
|
122
|
+
}
|
|
123
|
+
.slx-spinner {
|
|
124
|
+
width: 15px; height: 15px; flex: none;
|
|
125
|
+
border: 2px solid rgba(255,255,255,.35); border-top-color: #fff;
|
|
126
|
+
border-radius: 50%; animation: slx-spin .7s linear infinite;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/* ── Social ── */
|
|
130
|
+
.slx-social { display: grid; gap: 10px; margin-bottom: 16px; }
|
|
131
|
+
.slx-social-btn {
|
|
132
|
+
width: 100%; box-sizing: border-box;
|
|
133
|
+
font: inherit; font-size: 13.5px; font-weight: 550;
|
|
134
|
+
color: var(--slx-ink); background: var(--slx-bg);
|
|
135
|
+
border: 1px solid var(--slx-border); border-radius: var(--slx-radius);
|
|
136
|
+
padding: 10px 14px; cursor: pointer;
|
|
137
|
+
display: inline-flex; align-items: center; justify-content: center; gap: 10px;
|
|
138
|
+
transition: background .15s, border-color .15s;
|
|
139
|
+
}
|
|
140
|
+
.slx-social-btn:hover { background: color-mix(in srgb, var(--slx-ink) 4%, var(--slx-bg)); }
|
|
141
|
+
.slx-social-btn:focus-visible { outline: none; box-shadow: 0 0 0 3px var(--slx-accent-soft); }
|
|
142
|
+
|
|
143
|
+
/* ── Divider & footer ── */
|
|
144
|
+
.slx-divider {
|
|
145
|
+
display: flex; align-items: center; gap: 12px;
|
|
146
|
+
color: var(--slx-muted); font-size: 12px;
|
|
147
|
+
margin: 18px 0;
|
|
148
|
+
}
|
|
149
|
+
.slx-divider::before, .slx-divider::after {
|
|
150
|
+
content: ""; height: 1px; flex: 1; background: var(--slx-border);
|
|
151
|
+
}
|
|
152
|
+
.slx-footer { font-size: 13px; color: var(--slx-muted); margin-top: 20px; text-align: center; }
|
|
153
|
+
.slx-link {
|
|
154
|
+
color: var(--slx-accent); font-weight: 600; text-decoration: none; cursor: pointer;
|
|
155
|
+
background: none; border: none; font: inherit; font-size: inherit;
|
|
156
|
+
padding: 0; margin: 0;
|
|
157
|
+
}
|
|
158
|
+
.slx-link:hover { text-decoration: underline; color: var(--slx-accent-hover); }
|
|
159
|
+
.slx-link:focus-visible { outline: none; box-shadow: 0 0 0 3px var(--slx-accent-soft); border-radius: 4px; }
|
|
160
|
+
|
|
161
|
+
/* ── Success state ── */
|
|
162
|
+
.slx-success-icon {
|
|
163
|
+
width: 46px; height: 46px; border-radius: 50%;
|
|
164
|
+
display: flex; align-items: center; justify-content: center;
|
|
165
|
+
background: color-mix(in srgb, #34a853 12%, transparent);
|
|
166
|
+
color: #34a853; margin: 4px auto 18px;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/* ── User button ── */
|
|
170
|
+
.slx-userbtn-wrap { position: relative; display: inline-block; }
|
|
171
|
+
.slx-userbtn-avatar {
|
|
172
|
+
width: 36px; height: 36px; border-radius: 50%;
|
|
173
|
+
border: 1px solid var(--slx-border); cursor: pointer;
|
|
174
|
+
display: flex; align-items: center; justify-content: center;
|
|
175
|
+
font-size: 14px; font-weight: 650; color: #fff;
|
|
176
|
+
background: linear-gradient(135deg, var(--slx-accent), #9a6cf0);
|
|
177
|
+
padding: 0; overflow: hidden;
|
|
178
|
+
}
|
|
179
|
+
.slx-userbtn-avatar img { width: 100%; height: 100%; object-fit: cover; }
|
|
180
|
+
.slx-userbtn-avatar:focus-visible { outline: none; box-shadow: 0 0 0 3px var(--slx-accent-soft); }
|
|
181
|
+
.slx-menu {
|
|
182
|
+
position: absolute; right: 0; top: calc(100% + 8px);
|
|
183
|
+
min-width: 230px;
|
|
184
|
+
background: var(--slx-bg);
|
|
185
|
+
border: 1px solid var(--slx-border);
|
|
186
|
+
border-radius: var(--slx-radius);
|
|
187
|
+
box-shadow: 0 8px 30px rgba(10,10,20,.14);
|
|
188
|
+
overflow: hidden; z-index: 1000;
|
|
189
|
+
}
|
|
190
|
+
.slx-menu-header { padding: 14px 16px; border-bottom: 1px solid var(--slx-border); }
|
|
191
|
+
.slx-menu-name { font-size: 14px; font-weight: 600; margin: 0 0 2px; }
|
|
192
|
+
.slx-menu-email { font-size: 12.5px; color: var(--slx-muted); margin: 0; word-break: break-all; }
|
|
193
|
+
.slx-menu-item {
|
|
194
|
+
display: block; width: 100%; text-align: left; box-sizing: border-box;
|
|
195
|
+
font: inherit; font-size: 13.5px; color: var(--slx-ink);
|
|
196
|
+
background: none; border: none; padding: 10px 16px; cursor: pointer;
|
|
197
|
+
}
|
|
198
|
+
.slx-menu-item:hover { background: color-mix(in srgb, var(--slx-ink) 5%, transparent); }
|
|
199
|
+
.slx-menu-item:focus-visible { outline: none; box-shadow: inset 0 0 0 2px var(--slx-accent-soft); }
|
|
200
|
+
.slx-menu-item-danger { color: var(--slx-danger); }
|
|
201
|
+
`;
|
|
202
|
+
|
|
203
|
+
let injected = false;
|
|
204
|
+
|
|
205
|
+
/** Inject the SlyxUp stylesheet + DM Sans font once per document. */
|
|
206
|
+
export function injectStyles(): void {
|
|
207
|
+
if (injected || typeof document === 'undefined') return;
|
|
208
|
+
// DM Sans font link
|
|
209
|
+
if (!document.querySelector('link[href*="DM+Sans"]')) {
|
|
210
|
+
const fontLink = document.createElement('link');
|
|
211
|
+
fontLink.rel = 'stylesheet';
|
|
212
|
+
fontLink.href =
|
|
213
|
+
'https://fonts.googleapis.com/css2?family=DM+Sans:opsz,wght@9..40,300;9..40,400;9..40,500;9..40,600;9..40,700;9..40,800&family=Space+Grotesk:wght@400;500;600;700&display=swap';
|
|
214
|
+
document.head.appendChild(fontLink);
|
|
215
|
+
}
|
|
216
|
+
const style = document.createElement('style');
|
|
217
|
+
style.setAttribute('data-slyxup', 'styles');
|
|
218
|
+
style.textContent = CSS;
|
|
219
|
+
document.head.appendChild(style);
|
|
220
|
+
injected = true;
|
|
221
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
|
|
5
|
+
const mockSignIn = vi.fn().mockResolvedValue({ ok: true });
|
|
6
|
+
const mockSignUp = vi.fn().mockResolvedValue({ ok: true });
|
|
7
|
+
const mockSignOut = vi.fn().mockResolvedValue({ ok: true });
|
|
8
|
+
|
|
9
|
+
vi.mock('@slyxup/react', () => ({
|
|
10
|
+
useAuth: () => ({
|
|
11
|
+
isLoaded: true,
|
|
12
|
+
isSignedIn: false,
|
|
13
|
+
userId: null,
|
|
14
|
+
client: {},
|
|
15
|
+
signIn: mockSignIn,
|
|
16
|
+
signUp: mockSignUp,
|
|
17
|
+
signOut: mockSignOut,
|
|
18
|
+
}),
|
|
19
|
+
useUser: () => ({
|
|
20
|
+
isLoaded: true,
|
|
21
|
+
isSignedIn: false,
|
|
22
|
+
user: null,
|
|
23
|
+
isSignedOut: true,
|
|
24
|
+
reload: vi.fn(),
|
|
25
|
+
}),
|
|
26
|
+
useSession: () => ({
|
|
27
|
+
isLoaded: true,
|
|
28
|
+
isSignedIn: false,
|
|
29
|
+
session: null,
|
|
30
|
+
reload: vi.fn(),
|
|
31
|
+
}),
|
|
32
|
+
}));
|
|
33
|
+
|
|
34
|
+
import { SignIn } from '../src/components/SignIn/SignIn';
|
|
35
|
+
import { SignUp } from '../src/components/SignUp/SignUp';
|
|
36
|
+
import { SocialButtons } from '../src/components/SocialButtons/SocialButtons';
|
|
37
|
+
import { UserButton } from '../src/components/UserButton/UserButton';
|
|
38
|
+
|
|
39
|
+
describe('SignIn', () => {
|
|
40
|
+
beforeEach(() => vi.clearAllMocks());
|
|
41
|
+
|
|
42
|
+
it('renders sign in heading', () => {
|
|
43
|
+
render(<SignIn />);
|
|
44
|
+
expect(screen.getByRole('heading', { name: /sign in/i })).toBeTruthy();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('renders email and password inputs', () => {
|
|
48
|
+
render(<SignIn />);
|
|
49
|
+
expect(screen.getByLabelText(/email/i)).toBeTruthy();
|
|
50
|
+
expect(screen.getByLabelText(/password/i)).toBeTruthy();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('renders submit button', () => {
|
|
54
|
+
render(<SignIn />);
|
|
55
|
+
expect(screen.getByRole('button', { name: /sign in/i })).toBeTruthy();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('shows social buttons by default', () => {
|
|
59
|
+
render(<SignIn />);
|
|
60
|
+
expect(screen.getByText(/continue with google/i)).toBeTruthy();
|
|
61
|
+
expect(screen.getByText(/continue with github/i)).toBeTruthy();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('hides social buttons when social=false', () => {
|
|
65
|
+
render(<SignIn social={false} />);
|
|
66
|
+
expect(screen.queryByText(/continue with google/i)).toBeNull();
|
|
67
|
+
expect(screen.queryByText(/continue with github/i)).toBeNull();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('calls signIn on form submit', async () => {
|
|
71
|
+
render(<SignIn />);
|
|
72
|
+
fireEvent.change(screen.getByLabelText(/email/i), { target: { value: 'a@b.com' } });
|
|
73
|
+
fireEvent.change(screen.getByLabelText(/password/i), { target: { value: '12345678' } });
|
|
74
|
+
fireEvent.click(screen.getByRole('button', { name: /sign in/i }));
|
|
75
|
+
await waitFor(() => expect(mockSignIn).toHaveBeenCalledWith({ email: 'a@b.com', password: '12345678' }));
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('shows sign up link when onSignUpClick provided', () => {
|
|
79
|
+
const onSignUp = vi.fn();
|
|
80
|
+
render(<SignIn onSignUpClick={onSignUp} />);
|
|
81
|
+
expect(screen.getByText(/sign up/i)).toBeTruthy();
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe('SignUp', () => {
|
|
86
|
+
beforeEach(() => vi.clearAllMocks());
|
|
87
|
+
|
|
88
|
+
it('renders create your account heading', () => {
|
|
89
|
+
render(<SignUp />);
|
|
90
|
+
expect(screen.getByRole('heading', { name: /create your account/i })).toBeTruthy();
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('renders first name, email, password inputs', () => {
|
|
94
|
+
render(<SignUp />);
|
|
95
|
+
expect(screen.getByLabelText(/first name/i)).toBeTruthy();
|
|
96
|
+
expect(screen.getByLabelText(/email/i)).toBeTruthy();
|
|
97
|
+
expect(screen.getByLabelText(/password/i)).toBeTruthy();
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('shows social buttons by default', () => {
|
|
101
|
+
render(<SignUp />);
|
|
102
|
+
expect(screen.getByText(/continue with google/i)).toBeTruthy();
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
describe('SocialButtons', () => {
|
|
107
|
+
beforeEach(() => vi.clearAllMocks());
|
|
108
|
+
|
|
109
|
+
it('renders both providers by default', () => {
|
|
110
|
+
render(<SocialButtons />);
|
|
111
|
+
expect(screen.getByText(/continue with google/i)).toBeTruthy();
|
|
112
|
+
expect(screen.getByText(/continue with github/i)).toBeTruthy();
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('renders only specified providers', () => {
|
|
116
|
+
render(<SocialButtons providers={['google']} />);
|
|
117
|
+
expect(screen.getByText(/continue with google/i)).toBeTruthy();
|
|
118
|
+
expect(screen.queryByText(/continue with github/i)).toBeNull();
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
describe('UserButton', () => {
|
|
123
|
+
beforeEach(() => vi.clearAllMocks());
|
|
124
|
+
|
|
125
|
+
it('renders avatar', () => {
|
|
126
|
+
render(<UserButton />);
|
|
127
|
+
expect(screen.getByRole('button', { name: /account menu/i })).toBeTruthy();
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('opens dropdown on click', async () => {
|
|
131
|
+
render(<UserButton />);
|
|
132
|
+
fireEvent.click(screen.getByRole('button', { name: /account menu/i }));
|
|
133
|
+
await waitFor(() => expect(screen.getByText(/sign out/i)).toBeTruthy());
|
|
134
|
+
});
|
|
135
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { render } from '@testing-library/react';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { KeyholeMark, GoogleIcon, GitHubIcon, CheckIcon } from '../src/icons';
|
|
5
|
+
|
|
6
|
+
describe('KeyholeMark', () => {
|
|
7
|
+
it('renders an SVG', () => {
|
|
8
|
+
const { container } = render(<KeyholeMark />);
|
|
9
|
+
expect(container.querySelector('svg')).toBeTruthy();
|
|
10
|
+
});
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
describe('GoogleIcon', () => {
|
|
14
|
+
it('renders an SVG', () => {
|
|
15
|
+
const { container } = render(<GoogleIcon />);
|
|
16
|
+
expect(container.querySelector('svg')).toBeTruthy();
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
describe('GitHubIcon', () => {
|
|
21
|
+
it('renders an SVG', () => {
|
|
22
|
+
const { container } = render(<GitHubIcon />);
|
|
23
|
+
expect(container.querySelector('svg')).toBeTruthy();
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
describe('CheckIcon', () => {
|
|
28
|
+
it('renders an SVG', () => {
|
|
29
|
+
const { container } = render(<CheckIcon />);
|
|
30
|
+
expect(container.querySelector('svg')).toBeTruthy();
|
|
31
|
+
});
|
|
32
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { CSS, injectStyles } from '../src/styles';
|
|
3
|
+
|
|
4
|
+
describe('CSS', () => {
|
|
5
|
+
it('is a non-empty string', () => {
|
|
6
|
+
expect(typeof CSS).toBe('string');
|
|
7
|
+
expect(CSS.length).toBeGreaterThan(0);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it('contains .slyxup-root', () => {
|
|
11
|
+
expect(CSS).toContain('.slyxup-root');
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('contains --slx-accent', () => {
|
|
15
|
+
expect(CSS).toContain('--slx-accent');
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('contains dark mode media query', () => {
|
|
19
|
+
expect(CSS).toContain('prefers-color-scheme: dark');
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('contains @keyframes slx-shake', () => {
|
|
23
|
+
expect(CSS).toContain('@keyframes slx-shake');
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('contains @keyframes slx-spin', () => {
|
|
27
|
+
expect(CSS).toContain('@keyframes slx-spin');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('contains prefers-reduced-motion', () => {
|
|
31
|
+
expect(CSS).toContain('prefers-reduced-motion');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('contains card styles', () => {
|
|
35
|
+
expect(CSS).toContain('.slx-card');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('contains button styles', () => {
|
|
39
|
+
expect(CSS).toContain('.slx-btn');
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
describe('injectStyles', () => {
|
|
44
|
+
it('is a function', () => {
|
|
45
|
+
expect(typeof injectStyles).toBe('function');
|
|
46
|
+
});
|
|
47
|
+
});
|
package/vitest.config.ts
ADDED