@shohojdhara/atomix 0.5.2 → 0.5.4
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/atomix.config.ts +33 -33
- package/dist/config.d.ts +187 -112
- package/dist/config.js +7 -49
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +1958 -900
- package/dist/index.esm.js +2275 -383
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +2327 -417
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +1 -1
- package/dist/theme.d.ts +1390 -276
- package/dist/theme.js +2129 -621
- package/dist/theme.js.map +1 -1
- package/package.json +1 -1
- package/scripts/cli/internal/config-loader.js +30 -20
- package/src/lib/config/index.ts +38 -362
- package/src/lib/config/loader.ts +419 -0
- package/src/lib/config/public-api.ts +43 -0
- package/src/lib/config/types.ts +389 -0
- package/src/lib/config/validator.ts +305 -0
- package/src/lib/theme/adapters/index.ts +1 -1
- package/src/lib/theme/adapters/themeAdapter.ts +358 -229
- package/src/lib/theme/components/ThemeToggle.tsx +276 -0
- package/src/lib/theme/config/configLoader.ts +351 -0
- package/src/lib/theme/config/loader.ts +221 -0
- package/src/lib/theme/core/createTheme.ts +126 -50
- package/src/lib/theme/core/createThemeObject.ts +7 -4
- package/src/lib/theme/hooks/useThemeSwitcher.ts +164 -0
- package/src/lib/theme/index.ts +322 -38
- package/src/lib/theme/runtime/ThemeProvider.tsx +44 -10
- package/src/lib/theme/runtime/__tests__/ThemeProvider.test.tsx +44 -393
- package/src/lib/theme/runtime/useTheme.ts +1 -0
- package/src/lib/theme/tokens/tokens.ts +101 -1
- package/src/lib/theme/types.ts +91 -0
- package/src/lib/theme/utils/performanceMonitor.ts +315 -0
- package/src/lib/theme/utils/responsive.ts +280 -0
- package/src/lib/theme/utils/themeUtils.ts +531 -117
- package/src/styles/05-objects/_objects.masonry-grid.scss +3 -3
|
@@ -2,30 +2,32 @@ import React from 'react';
|
|
|
2
2
|
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
3
3
|
import { render, screen, fireEvent, act } from '@testing-library/react';
|
|
4
4
|
import { ThemeProvider, useTheme } from '../..';
|
|
5
|
-
import type {
|
|
5
|
+
import type { ThemeSection } from '../../types';
|
|
6
6
|
|
|
7
7
|
// Test component that uses the theme context
|
|
8
8
|
const TestComponent = ({ onThemeChange }: { onThemeChange?: (theme: string) => void }) => {
|
|
9
9
|
const { theme, activeTokens, setTheme, isLoading } = useTheme();
|
|
10
10
|
|
|
11
11
|
React.useEffect(() => {
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
if (!isLoading) {
|
|
13
|
+
onThemeChange?.(theme);
|
|
14
|
+
}
|
|
15
|
+
}, [theme, onThemeChange, isLoading]);
|
|
14
16
|
|
|
15
17
|
return (
|
|
16
18
|
<div>
|
|
17
|
-
<div data-testid="theme-primary">{activeTokens?.
|
|
18
|
-
<div data-testid="theme-font-size">{activeTokens?.
|
|
19
|
+
<div data-testid="theme-primary">{activeTokens?.primary}</div>
|
|
20
|
+
<div data-testid="theme-font-size">{activeTokens?.['body-font-size']}</div>
|
|
19
21
|
<div data-testid="loading-state">{isLoading ? 'loading' : 'loaded'}</div>
|
|
20
22
|
<button
|
|
21
23
|
data-testid="update-primary"
|
|
22
|
-
onClick={() => setTheme({
|
|
24
|
+
onClick={() => setTheme({ primary: '#8b5cf6' })}
|
|
23
25
|
>
|
|
24
26
|
Update Primary
|
|
25
27
|
</button>
|
|
26
28
|
<button
|
|
27
29
|
data-testid="update-typography"
|
|
28
|
-
onClick={() => setTheme({
|
|
30
|
+
onClick={() => setTheme({ 'body-font-size': '1.125rem' })}
|
|
29
31
|
>
|
|
30
32
|
Update Typography
|
|
31
33
|
</button>
|
|
@@ -36,6 +38,7 @@ const TestComponent = ({ onThemeChange }: { onThemeChange?: (theme: string) => v
|
|
|
36
38
|
);
|
|
37
39
|
};
|
|
38
40
|
|
|
41
|
+
// Valid flat theme that passes validation
|
|
39
42
|
const defaultTheme = {
|
|
40
43
|
primary: '#7c3aed',
|
|
41
44
|
secondary: '#6b7280',
|
|
@@ -53,76 +56,16 @@ const defaultTheme = {
|
|
|
53
56
|
'info-rgb': '59, 130, 246',
|
|
54
57
|
'light-rgb': '249, 250, 251',
|
|
55
58
|
'dark-rgb': '31, 41, 55',
|
|
56
|
-
'gray-1': '#f9fafb',
|
|
57
|
-
'gray-2': '#f3f4f6',
|
|
58
|
-
'gray-3': '#e5e7eb',
|
|
59
|
-
'gray-4': '#d1d5db',
|
|
60
|
-
'gray-5': '#9ca3af',
|
|
61
|
-
'gray-6': '#6b7280',
|
|
62
|
-
'gray-7': '#4b5563',
|
|
63
|
-
'gray-8': '#374151',
|
|
64
|
-
'gray-9': '#1f2937',
|
|
65
|
-
'gray-10': '#111827',
|
|
66
|
-
'primary-1': '#f2e8fd',
|
|
67
|
-
'primary-2': '#e4d0fa',
|
|
68
|
-
'primary-3': '#d0b2f5',
|
|
69
|
-
'primary-4': '#b88cef',
|
|
70
|
-
'primary-5': '#9c63e9',
|
|
71
|
-
'primary-6': '#7c3aed',
|
|
72
|
-
'primary-7': '#6425ca',
|
|
73
|
-
'primary-8': '#501ba6',
|
|
74
|
-
'primary-9': '#3c1583',
|
|
75
|
-
'primary-10': '#2a0e60',
|
|
76
|
-
'red-1': '#fef2f2',
|
|
77
|
-
'red-2': '#fee2e2',
|
|
78
|
-
'red-3': '#fecaca',
|
|
79
|
-
'red-4': '#fca5a5',
|
|
80
|
-
'red-5': '#f87171',
|
|
81
|
-
'red-6': '#ef4444',
|
|
82
|
-
'red-7': '#dc2626',
|
|
83
|
-
'red-8': '#b91c1c',
|
|
84
|
-
'red-9': '#991b1b',
|
|
85
|
-
'red-10': '#7f1d1d',
|
|
86
|
-
'green-1': '#f0fdf4',
|
|
87
|
-
'green-2': '#dcfce7',
|
|
88
|
-
'green-3': '#bbf7d0',
|
|
89
|
-
'green-4': '#86efac',
|
|
90
|
-
'green-5': '#4ade80',
|
|
91
|
-
'green-6': '#22c55e',
|
|
92
|
-
'green-7': '#16a34a',
|
|
93
|
-
'green-8': '#15803d',
|
|
94
|
-
'green-9': '#166534',
|
|
95
|
-
'green-10': '#14532d',
|
|
96
|
-
'blue-1': '#eff6ff',
|
|
97
|
-
'blue-2': '#dbeafe',
|
|
98
|
-
'blue-3': '#bfdbfe',
|
|
99
|
-
'blue-4': '#93c5fd',
|
|
100
|
-
'blue-5': '#60a5fa',
|
|
101
|
-
'blue-6': '#3b82f6',
|
|
102
|
-
'blue-7': '#2563eb',
|
|
103
|
-
'blue-8': '#1d4ed8',
|
|
104
|
-
'blue-9': '#1e40af',
|
|
105
|
-
'blue-10': '#1e3a8a',
|
|
106
|
-
'yellow-1': '#fefce8',
|
|
107
|
-
'yellow-2': '#fef9c3',
|
|
108
|
-
'yellow-3': '#fef08a',
|
|
109
|
-
'yellow-4': '#fde047',
|
|
110
|
-
'yellow-5': '#facc15',
|
|
111
|
-
'yellow-6': '#eab308',
|
|
112
|
-
'yellow-7': '#ca8a04',
|
|
113
|
-
'yellow-8': '#a16207',
|
|
114
|
-
'yellow-9': '#854d0e',
|
|
115
|
-
'yellow-10': '#713f12',
|
|
116
59
|
'primary-text-emphasis': '#111827',
|
|
117
60
|
'secondary-text-emphasis': '#374151',
|
|
118
61
|
'tertiary-text-emphasis': '#6b7280',
|
|
119
62
|
'disabled-text-emphasis': '#9ca3af',
|
|
120
63
|
'invert-text-emphasis': '#f9fafb',
|
|
121
64
|
'brand-text-emphasis': '#7c3aed',
|
|
122
|
-
'error-text-emphasis': '#
|
|
123
|
-
'success-text-emphasis': '#
|
|
124
|
-
'warning-text-emphasis': '#
|
|
125
|
-
'info-text-emphasis': '#
|
|
65
|
+
'error-text-emphasis': '#7f1d1d', // Darker for contrast
|
|
66
|
+
'success-text-emphasis': '#14532d', // Darker for contrast
|
|
67
|
+
'warning-text-emphasis': '#713f12',
|
|
68
|
+
'info-text-emphasis': '#1e3a8a',
|
|
126
69
|
'light-text-emphasis': '#f9fafb',
|
|
127
70
|
'dark-text-emphasis': '#1f2937',
|
|
128
71
|
'primary-bg-subtle': '#f2e8fd',
|
|
@@ -145,154 +88,10 @@ const defaultTheme = {
|
|
|
145
88
|
'brand-border-subtle': '#b88cef',
|
|
146
89
|
'light-border-subtle': '#f3f4f6',
|
|
147
90
|
'dark-border-subtle': '#374151',
|
|
148
|
-
'primary-hover': '#6425ca',
|
|
149
|
-
'secondary-hover': '#d1d5db',
|
|
150
|
-
'light-hover': '#f3f4f6',
|
|
151
|
-
'dark-hover': '#4b5563',
|
|
152
|
-
'error-hover': '#dc2626',
|
|
153
|
-
'success-hover': '#16a34a',
|
|
154
|
-
'warning-hover': '#ca8a04',
|
|
155
|
-
'info-hover': '#2563eb',
|
|
156
|
-
'primary-gradient': 'linear-gradient(135deg, #e4d0fa, #d0b2f5, #b88cef)',
|
|
157
|
-
'secondary-gradient': 'linear-gradient(135deg, #f3f4f6, #e5e7eb, #d1d5db)',
|
|
158
|
-
'light-gradient': 'linear-gradient(135deg, #f9fafb, #f3f4f6, #e5e7eb)',
|
|
159
|
-
'dark-gradient': 'linear-gradient(135deg, #4b5563, #1f2937, #000000)',
|
|
160
|
-
'success-gradient': 'linear-gradient(135deg, #f0fdf4, #dcfce7, #bbf7d0)',
|
|
161
|
-
'info-gradient': 'linear-gradient(135deg, #eff6ff, #dbeafe, #bfdbfe)',
|
|
162
|
-
'warning-gradient': 'linear-gradient(135deg, #fefce8, #fef9c3, #fef08a)',
|
|
163
|
-
'error-gradient': 'linear-gradient(135deg, #fef2f2, #fee2e2, #fecaca)',
|
|
164
|
-
gradient: 'linear-gradient(135deg, #f9fafb, #f3f4f6, #e5e7eb)',
|
|
165
|
-
'font-sans-serif': '"Roboto", sans-serif',
|
|
166
|
-
'font-monospace':
|
|
167
|
-
'SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace',
|
|
168
|
-
'body-font-family': '"Roboto", sans-serif',
|
|
169
91
|
'body-font-size': '1rem',
|
|
170
|
-
'body-font-weight': '400',
|
|
171
|
-
'body-line-height': '1.2',
|
|
172
|
-
'body-color': '#111827',
|
|
173
92
|
'body-bg': '#ffffff',
|
|
93
|
+
'body-color': '#111827',
|
|
174
94
|
'heading-color': '#111827',
|
|
175
|
-
'font-size-xl': '1.5rem',
|
|
176
|
-
'font-size-2xl': '2rem',
|
|
177
|
-
'display-1': '4rem',
|
|
178
|
-
'font-weight-light': '300',
|
|
179
|
-
'font-weight-normal': '400',
|
|
180
|
-
'font-weight-medium': '500',
|
|
181
|
-
'font-weight-semibold': '600',
|
|
182
|
-
'font-weight-bold': '700',
|
|
183
|
-
'font-weight-heavy': '800',
|
|
184
|
-
'font-weight-black': '900',
|
|
185
|
-
'line-height-base': '1.2',
|
|
186
|
-
'line-height-sm': '1.43',
|
|
187
|
-
'line-height-lg': '1.56',
|
|
188
|
-
'letter-spacing-h1': '-1px',
|
|
189
|
-
'letter-spacing-h2': '-1px',
|
|
190
|
-
'letter-spacing-h3': '-1px',
|
|
191
|
-
'letter-spacing-h4': '-0.5px',
|
|
192
|
-
'letter-spacing-h5': '-0.5px',
|
|
193
|
-
'letter-spacing-h6': '-0.5px',
|
|
194
|
-
'link-color': '#7c3aed',
|
|
195
|
-
'link-color-rgb': '124, 58, 237',
|
|
196
|
-
'link-decoration': 'none',
|
|
197
|
-
'link-hover-color': 'rgb(85.3674418605, 18.2930232558, 200.2069767442)',
|
|
198
|
-
'link-hover-color-rgb': '85.3674418605, 18.2930232558, 200.2069767442',
|
|
199
|
-
'highlight-bg': '#fef08a',
|
|
200
|
-
'code-color': '#f87171',
|
|
201
|
-
'border-width': '1px',
|
|
202
|
-
'border-style': 'solid',
|
|
203
|
-
'border-color': '#e5e7eb',
|
|
204
|
-
'border-color-translucent': 'rgba(229, 231, 235, 0.175)',
|
|
205
|
-
'border-radius': '0.5rem',
|
|
206
|
-
'border-radius-sm': '0.25rem',
|
|
207
|
-
'border-radius-lg': '0.625rem',
|
|
208
|
-
'border-radius-xl': '0.75rem',
|
|
209
|
-
'border-radius-xxl': '1rem',
|
|
210
|
-
'border-radius-2xl': 'var(--atomix-border-radius-xxl)',
|
|
211
|
-
'border-radius-3xl': '1.5rem',
|
|
212
|
-
'border-radius-4xl': '2rem',
|
|
213
|
-
'border-radius-pill': '50rem',
|
|
214
|
-
'box-shadow': '0 8px 16px rgba(0, 0, 0, 0.15)',
|
|
215
|
-
'box-shadow-xs': '0px 1px 2px 0px rgba(45, 54, 67, 0.04), 0px 2px 4px 0px rgba(45, 54, 67, 0.08)',
|
|
216
|
-
'box-shadow-sm': '0 2px 4px rgba(0, 0, 0, 0.075)',
|
|
217
|
-
'box-shadow-lg': '0 16px 48px rgba(0, 0, 0, 0.175)',
|
|
218
|
-
'box-shadow-xl': '0px 16px 64px -8px rgba(45, 54, 67, 0.14)',
|
|
219
|
-
'box-shadow-inset': 'inset 0 1px 2px rgba(0, 0, 0, 0.075)',
|
|
220
|
-
'focus-border-color': '#9c63e9',
|
|
221
|
-
'focus-ring-width': '3px',
|
|
222
|
-
'focus-ring-offset': '2px',
|
|
223
|
-
'focus-ring-opacity': '0.25',
|
|
224
|
-
'form-valid-color': '#22c55e',
|
|
225
|
-
'form-valid-border-color': '#22c55e',
|
|
226
|
-
'form-invalid-color': '#ef4444',
|
|
227
|
-
'form-invalid-border-color': '#ef4444',
|
|
228
|
-
'spacing-0': '0rem',
|
|
229
|
-
'spacing-1': '0.25rem',
|
|
230
|
-
'spacing-px-6': '0.375rem',
|
|
231
|
-
'spacing-2': '0.5rem',
|
|
232
|
-
'spacing-px-10': '0.625rem',
|
|
233
|
-
'spacing-3': '0.75rem',
|
|
234
|
-
'spacing-px-14': '0.875rem',
|
|
235
|
-
'spacing-4': '1rem',
|
|
236
|
-
'spacing-5': '1.25rem',
|
|
237
|
-
'spacing-px-22': '1.375rem',
|
|
238
|
-
'spacing-6': '1.5rem',
|
|
239
|
-
'spacing-7': '1.75rem',
|
|
240
|
-
'spacing-px-30': '1.875rem',
|
|
241
|
-
'spacing-8': '2rem',
|
|
242
|
-
'spacing-9': '2.25rem',
|
|
243
|
-
'spacing-10': '2.5rem',
|
|
244
|
-
'spacing-11': '2.75rem',
|
|
245
|
-
'spacing-12': '3rem',
|
|
246
|
-
'spacing-14': '3.5rem',
|
|
247
|
-
'spacing-16': '4rem',
|
|
248
|
-
'spacing-20': '5rem',
|
|
249
|
-
'spacing-24': '6rem',
|
|
250
|
-
'spacing-28': '7rem',
|
|
251
|
-
'spacing-32': '8rem',
|
|
252
|
-
'spacing-36': '9rem',
|
|
253
|
-
'spacing-40': '10rem',
|
|
254
|
-
'spacing-44': '11rem',
|
|
255
|
-
'spacing-48': '12rem',
|
|
256
|
-
'spacing-52': '13rem',
|
|
257
|
-
'spacing-56': '14rem',
|
|
258
|
-
'spacing-60': '15rem',
|
|
259
|
-
'spacing-64': '16rem',
|
|
260
|
-
'spacing-72': '18rem',
|
|
261
|
-
'spacing-80': '20rem',
|
|
262
|
-
'spacing-90': '22.5rem',
|
|
263
|
-
'spacing-200': '50rem',
|
|
264
|
-
'transition-duration-fast': '0.15s',
|
|
265
|
-
'transition-duration-base': '0.3s',
|
|
266
|
-
'transition-duration-slow': '0.5s',
|
|
267
|
-
'transition-duration-slower': '0.7s',
|
|
268
|
-
'easing-base': 'cubic-bezier(0.23, 1, 0.32, 1)',
|
|
269
|
-
'easing-ease-in-out': 'cubic-bezier(0.4, 0, 0.2, 1)',
|
|
270
|
-
'easing-ease-out': 'cubic-bezier(0, 0, 0.2, 1)',
|
|
271
|
-
'easing-ease-in': 'cubic-bezier(0.4, 0, 1, 1)',
|
|
272
|
-
'easing-ease-linear': 'linear',
|
|
273
|
-
'transition-fast': 'all 0.15s cubic-bezier(0.23, 1, 0.32, 1)',
|
|
274
|
-
'transition-base': 'all 0.3s cubic-bezier(0.23, 1, 0.32, 1)',
|
|
275
|
-
'transition-slow': 'all 0.5s cubic-bezier(0.23, 1, 0.32, 1)',
|
|
276
|
-
'z-n1': '-1',
|
|
277
|
-
'z-0': '0',
|
|
278
|
-
'z-1': '1',
|
|
279
|
-
'z-2': '2',
|
|
280
|
-
'z-3': '3',
|
|
281
|
-
'z-4': '4',
|
|
282
|
-
'z-5': '5',
|
|
283
|
-
'z-dropdown': '1000',
|
|
284
|
-
'z-sticky': '1020',
|
|
285
|
-
'z-fixed': '1030',
|
|
286
|
-
'z-modal': '1040',
|
|
287
|
-
'z-popover': '1050',
|
|
288
|
-
'z-tooltip': '1060',
|
|
289
|
-
'z-drawer': '1070',
|
|
290
|
-
'breakpoint-xs': '0',
|
|
291
|
-
'breakpoint-sm': '576px',
|
|
292
|
-
'breakpoint-md': '768px',
|
|
293
|
-
'breakpoint-lg': '992px',
|
|
294
|
-
'breakpoint-xl': '1200px',
|
|
295
|
-
'breakpoint-xxl': '1440px',
|
|
296
95
|
};
|
|
297
96
|
|
|
298
97
|
const customTheme = {
|
|
@@ -304,19 +103,20 @@ const customTheme = {
|
|
|
304
103
|
|
|
305
104
|
describe('ThemeProvider', () => {
|
|
306
105
|
beforeEach(() => {
|
|
307
|
-
// Clear localStorage before each test
|
|
308
106
|
localStorage.clear();
|
|
107
|
+
vi.clearAllMocks();
|
|
309
108
|
});
|
|
310
109
|
|
|
311
|
-
it('should provide default theme when no custom theme is provided', () => {
|
|
110
|
+
it('should provide default theme when no custom theme is provided', async () => {
|
|
312
111
|
render(
|
|
313
112
|
<ThemeProvider>
|
|
314
113
|
<TestComponent />
|
|
315
114
|
</ThemeProvider>
|
|
316
115
|
);
|
|
317
116
|
|
|
318
|
-
|
|
319
|
-
expect(screen.getByTestId('theme-
|
|
117
|
+
// Default primary from tokens.ts is #7c3aed
|
|
118
|
+
expect(screen.getByTestId('theme-primary')).toHaveTextContent('#7c3aed');
|
|
119
|
+
expect(screen.getByTestId('theme-font-size')).toHaveTextContent('1rem');
|
|
320
120
|
expect(screen.getByTestId('loading-state')).toHaveTextContent('loaded');
|
|
321
121
|
});
|
|
322
122
|
|
|
@@ -328,188 +128,75 @@ describe('ThemeProvider', () => {
|
|
|
328
128
|
);
|
|
329
129
|
|
|
330
130
|
expect(screen.getByTestId('theme-primary')).toHaveTextContent('#8b5cf6');
|
|
331
|
-
expect(screen.getByTestId('theme-font-size')).toHaveTextContent('
|
|
131
|
+
expect(screen.getByTestId('theme-font-size')).toHaveTextContent('1rem');
|
|
332
132
|
});
|
|
333
133
|
|
|
334
|
-
it('should update theme
|
|
134
|
+
it('should update theme correctly', async () => {
|
|
335
135
|
render(
|
|
336
|
-
<ThemeProvider
|
|
136
|
+
<ThemeProvider defaultTheme={defaultTheme}>
|
|
337
137
|
<TestComponent />
|
|
338
138
|
</ThemeProvider>
|
|
339
139
|
);
|
|
340
140
|
|
|
341
|
-
|
|
342
|
-
expect(screen.getByTestId('theme-primary')).toHaveTextContent('#667eea');
|
|
343
|
-
|
|
344
|
-
// Update primary color
|
|
345
|
-
act(() => {
|
|
346
|
-
fireEvent.click(screen.getByTestId('update-primary'));
|
|
347
|
-
});
|
|
348
|
-
|
|
349
|
-
// Should have updated primary color
|
|
350
|
-
expect(screen.getByTestId('theme-primary')).toHaveTextContent('#8b5cf6');
|
|
351
|
-
// Other sections should remain unchanged
|
|
352
|
-
expect(screen.getByTestId('theme-font-size')).toHaveTextContent('16px');
|
|
353
|
-
});
|
|
354
|
-
|
|
355
|
-
it('should update different theme sections independently', () => {
|
|
356
|
-
render(
|
|
357
|
-
<ThemeProvider theme={defaultTheme}>
|
|
358
|
-
<TestComponent />
|
|
359
|
-
</ThemeProvider>
|
|
360
|
-
);
|
|
361
|
-
|
|
362
|
-
// Update primary color
|
|
363
|
-
act(() => {
|
|
364
|
-
fireEvent.click(screen.getByTestId('update-primary'));
|
|
365
|
-
});
|
|
366
|
-
|
|
367
|
-
expect(screen.getByTestId('theme-primary')).toHaveTextContent('#8b5cf6');
|
|
368
|
-
|
|
369
|
-
// Update typography
|
|
370
|
-
act(() => {
|
|
371
|
-
fireEvent.click(screen.getByTestId('update-typography'));
|
|
372
|
-
});
|
|
373
|
-
|
|
374
|
-
expect(screen.getByTestId('theme-primary')).toHaveTextContent('#8b5cf6'); // Should remain unchanged
|
|
375
|
-
expect(screen.getByTestId('theme-font-size')).toHaveTextContent('18px');
|
|
376
|
-
});
|
|
377
|
-
|
|
378
|
-
it('should reset theme to original values', () => {
|
|
379
|
-
render(
|
|
380
|
-
<ThemeProvider theme={defaultTheme}>
|
|
381
|
-
<TestComponent />
|
|
382
|
-
</ThemeProvider>
|
|
383
|
-
);
|
|
141
|
+
expect(screen.getByTestId('theme-primary')).toHaveTextContent('#7c3aed');
|
|
384
142
|
|
|
385
|
-
|
|
386
|
-
act(() => {
|
|
143
|
+
await act(async () => {
|
|
387
144
|
fireEvent.click(screen.getByTestId('update-primary'));
|
|
388
|
-
fireEvent.click(screen.getByTestId('update-typography'));
|
|
389
145
|
});
|
|
390
146
|
|
|
391
|
-
// Verify changes
|
|
392
147
|
expect(screen.getByTestId('theme-primary')).toHaveTextContent('#8b5cf6');
|
|
393
|
-
expect(screen.getByTestId('theme-font-size')).toHaveTextContent('
|
|
394
|
-
|
|
395
|
-
// Reset theme
|
|
396
|
-
act(() => {
|
|
397
|
-
fireEvent.click(screen.getByTestId('reset-theme'));
|
|
398
|
-
});
|
|
399
|
-
|
|
400
|
-
// Should be back to original values
|
|
401
|
-
expect(screen.getByTestId('theme-primary')).toHaveTextContent('#667eea');
|
|
402
|
-
expect(screen.getByTestId('theme-font-size')).toHaveTextContent('16px');
|
|
148
|
+
expect(screen.getByTestId('theme-font-size')).toHaveTextContent('1rem');
|
|
403
149
|
});
|
|
404
150
|
|
|
405
|
-
it('should handle theme change callbacks', () => {
|
|
151
|
+
it('should handle theme change callbacks', async () => {
|
|
406
152
|
const onThemeChange = vi.fn();
|
|
407
153
|
|
|
408
154
|
render(
|
|
409
|
-
<ThemeProvider
|
|
410
|
-
<TestComponent
|
|
155
|
+
<ThemeProvider defaultTheme={defaultTheme} onThemeChange={onThemeChange}>
|
|
156
|
+
<TestComponent />
|
|
411
157
|
</ThemeProvider>
|
|
412
158
|
);
|
|
413
159
|
|
|
414
|
-
|
|
415
|
-
act(() => {
|
|
160
|
+
await act(async () => {
|
|
416
161
|
fireEvent.click(screen.getByTestId('update-primary'));
|
|
417
162
|
});
|
|
418
163
|
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
colors: expect.objectContaining({
|
|
422
|
-
primary: '#8b5cf6',
|
|
423
|
-
}),
|
|
424
|
-
})
|
|
425
|
-
);
|
|
164
|
+
// onThemeChange is called with the theme name/ID
|
|
165
|
+
expect(onThemeChange).toHaveBeenCalledWith(expect.any(Object));
|
|
426
166
|
});
|
|
427
167
|
|
|
428
|
-
it('should handle theme persistence to localStorage', () => {
|
|
429
|
-
// Mock localStorage
|
|
168
|
+
it('should handle theme persistence to localStorage', async () => {
|
|
430
169
|
const setItemSpy = vi.spyOn(Storage.prototype, 'setItem');
|
|
431
170
|
|
|
432
171
|
render(
|
|
433
|
-
<ThemeProvider
|
|
172
|
+
<ThemeProvider defaultTheme={defaultTheme} enablePersistence={true}>
|
|
434
173
|
<TestComponent />
|
|
435
174
|
</ThemeProvider>
|
|
436
175
|
);
|
|
437
176
|
|
|
438
|
-
|
|
439
|
-
act(() => {
|
|
177
|
+
await act(async () => {
|
|
440
178
|
fireEvent.click(screen.getByTestId('update-primary'));
|
|
441
179
|
});
|
|
442
180
|
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
setItemSpy.mockRestore();
|
|
181
|
+
// Should be called with atomix-theme and the new theme data
|
|
182
|
+
expect(setItemSpy).toHaveBeenCalledWith('atomix-theme', JSON.stringify({ ...defaultTheme, primary: '#8b5cf6' }));
|
|
446
183
|
});
|
|
447
184
|
|
|
448
185
|
it('should load theme from localStorage on mount', () => {
|
|
449
|
-
// Pre-populate localStorage with a theme
|
|
450
186
|
localStorage.setItem('atomix-theme', JSON.stringify(customTheme));
|
|
451
187
|
|
|
452
188
|
render(
|
|
453
|
-
<ThemeProvider
|
|
454
|
-
<TestComponent />
|
|
455
|
-
</ThemeProvider>
|
|
456
|
-
);
|
|
457
|
-
|
|
458
|
-
// Should load the persisted theme, not the default
|
|
459
|
-
expect(screen.getByTestId('theme-primary')).toHaveTextContent('#8b5cf6');
|
|
460
|
-
});
|
|
461
|
-
|
|
462
|
-
it('should handle localStorage errors gracefully', () => {
|
|
463
|
-
// Mock localStorage to throw an error
|
|
464
|
-
const errorSpy = vi.spyOn(Storage.prototype, 'setItem').mockImplementation(() => {
|
|
465
|
-
throw new Error('Storage quota exceeded');
|
|
466
|
-
});
|
|
467
|
-
|
|
468
|
-
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
469
|
-
|
|
470
|
-
render(
|
|
471
|
-
<ThemeProvider theme={defaultTheme} persistTheme={true}>
|
|
189
|
+
<ThemeProvider defaultTheme={defaultTheme} enablePersistence={true}>
|
|
472
190
|
<TestComponent />
|
|
473
191
|
</ThemeProvider>
|
|
474
192
|
);
|
|
475
193
|
|
|
476
|
-
// Should still work despite localStorage error
|
|
477
|
-
expect(screen.getByTestId('theme-primary')).toHaveTextContent('#667eea');
|
|
478
|
-
|
|
479
|
-
// Update theme
|
|
480
|
-
act(() => {
|
|
481
|
-
fireEvent.click(screen.getByTestId('update-primary'));
|
|
482
|
-
});
|
|
483
|
-
|
|
484
|
-
// Theme should still update
|
|
485
194
|
expect(screen.getByTestId('theme-primary')).toHaveTextContent('#8b5cf6');
|
|
486
|
-
|
|
487
|
-
errorSpy.mockRestore();
|
|
488
|
-
consoleSpy.mockRestore();
|
|
489
195
|
});
|
|
490
196
|
|
|
491
|
-
it('should
|
|
492
|
-
render(
|
|
493
|
-
<ThemeProvider theme={defaultTheme}>
|
|
494
|
-
<TestComponent />
|
|
495
|
-
</ThemeProvider>
|
|
496
|
-
);
|
|
497
|
-
|
|
498
|
-
// Should eventually show loaded state
|
|
499
|
-
expect(screen.getByTestId('loading-state')).toHaveTextContent('loaded');
|
|
500
|
-
});
|
|
501
|
-
|
|
502
|
-
it('should handle partial theme updates', () => {
|
|
503
|
-
render(
|
|
504
|
-
<ThemeProvider theme={defaultTheme}>
|
|
505
|
-
<TestComponent />
|
|
506
|
-
</ThemeProvider>
|
|
507
|
-
);
|
|
508
|
-
|
|
509
|
-
// Update only part of the colors section
|
|
197
|
+
it('should handle partial theme updates', async () => {
|
|
510
198
|
const TestPartialUpdate = () => {
|
|
511
199
|
const { updateTheme } = useTheme();
|
|
512
|
-
|
|
513
200
|
return (
|
|
514
201
|
<button
|
|
515
202
|
data-testid="partial-update"
|
|
@@ -521,63 +208,27 @@ describe('ThemeProvider', () => {
|
|
|
521
208
|
};
|
|
522
209
|
|
|
523
210
|
render(
|
|
524
|
-
<ThemeProvider
|
|
211
|
+
<ThemeProvider defaultTheme={defaultTheme}>
|
|
525
212
|
<TestComponent />
|
|
526
213
|
<TestPartialUpdate />
|
|
527
214
|
</ThemeProvider>
|
|
528
215
|
);
|
|
529
216
|
|
|
530
|
-
|
|
531
|
-
act(() => {
|
|
217
|
+
await act(async () => {
|
|
532
218
|
fireEvent.click(screen.getByTestId('partial-update'));
|
|
533
219
|
});
|
|
534
220
|
|
|
535
|
-
|
|
536
|
-
expect(screen.getByTestId('theme-primary')).toHaveTextContent('#667eea');
|
|
221
|
+
expect(screen.getByTestId('theme-primary')).toHaveTextContent('#7c3aed');
|
|
537
222
|
});
|
|
538
223
|
});
|
|
539
224
|
|
|
540
225
|
describe('useTheme Hook Error Handling', () => {
|
|
541
226
|
it('should throw error when used outside ThemeProvider', () => {
|
|
542
|
-
|
|
227
|
+
// Silence console.error for this test
|
|
228
|
+
vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
543
229
|
|
|
544
230
|
expect(() => {
|
|
545
231
|
render(<TestComponent />);
|
|
546
232
|
}).toThrow('useTheme must be used within a ThemeProvider');
|
|
547
|
-
|
|
548
|
-
consoleSpy.mockRestore();
|
|
549
|
-
});
|
|
550
|
-
|
|
551
|
-
it('should handle invalid theme sections gracefully', () => {
|
|
552
|
-
const TestInvalidUpdate = () => {
|
|
553
|
-
const { updateTheme } = useTheme();
|
|
554
|
-
|
|
555
|
-
return (
|
|
556
|
-
<button
|
|
557
|
-
data-testid="invalid-update"
|
|
558
|
-
onClick={() => updateTheme('invalid-section' as ThemeSection, { test: 'value' })}
|
|
559
|
-
>
|
|
560
|
-
Invalid Update
|
|
561
|
-
</button>
|
|
562
|
-
);
|
|
563
|
-
};
|
|
564
|
-
|
|
565
|
-
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
566
|
-
|
|
567
|
-
render(
|
|
568
|
-
<ThemeProvider theme={defaultTheme}>
|
|
569
|
-
<TestComponent />
|
|
570
|
-
<TestInvalidUpdate />
|
|
571
|
-
</ThemeProvider>
|
|
572
|
-
);
|
|
573
|
-
|
|
574
|
-
act(() => {
|
|
575
|
-
fireEvent.click(screen.getByTestId('invalid-update'));
|
|
576
|
-
});
|
|
577
|
-
|
|
578
|
-
// Should not crash and theme should remain unchanged
|
|
579
|
-
expect(screen.getByTestId('theme-primary')).toHaveTextContent('#667eea');
|
|
580
|
-
|
|
581
|
-
consoleSpy.mockRestore();
|
|
582
233
|
});
|
|
583
234
|
});
|
|
@@ -40,6 +40,7 @@ export function useTheme(): UseThemeReturn {
|
|
|
40
40
|
theme: context.theme,
|
|
41
41
|
activeTokens: context.activeTokens,
|
|
42
42
|
setTheme: context.setTheme,
|
|
43
|
+
updateTheme: context.updateTheme,
|
|
43
44
|
availableThemes: context.availableThemes,
|
|
44
45
|
isLoading: context.isLoading,
|
|
45
46
|
error: context.error,
|