Skip to content

Commit 48656a5

Browse files
committed
test(theme): add unit tests for light/dark mode brand colors
- Test object format with separate light/dark values - Test separate CSS block generation for themes - Test validation caching to avoid duplicate warnings - Test mixed string and object brand color support
1 parent 891a5b6 commit 48656a5

File tree

1 file changed

+153
-0
lines changed

1 file changed

+153
-0
lines changed

packages/ui/src/providers/__tests__/ThemeProvider.test.tsx

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,159 @@ describe('ThemeProvider', () => {
401401
});
402402
});
403403

404+
describe('light/dark mode brand color variants', () => {
405+
it('should support object format with light/dark variants', () => {
406+
render(
407+
<ThemeProvider
408+
brandColors={{
409+
primary: { light: '#6366F1', dark: '#818CF8' },
410+
}}
411+
>
412+
<div>Test</div>
413+
</ThemeProvider>
414+
);
415+
416+
const styleElement = document.querySelector('[data-ainativekit-brand-colors]');
417+
// Should contain both light and dark mode colors
418+
expect(styleElement?.textContent).toContain('#6366F1');
419+
expect(styleElement?.textContent).toContain('#818CF8');
420+
});
421+
422+
it('should generate separate CSS blocks for light and dark themes', () => {
423+
render(
424+
<ThemeProvider
425+
brandColors={{
426+
primary: { light: '#059669', dark: '#34D399' },
427+
}}
428+
>
429+
<div>Test</div>
430+
</ThemeProvider>
431+
);
432+
433+
const styleElement = document.querySelector('[data-ainativekit-brand-colors]');
434+
const css = styleElement?.textContent || '';
435+
436+
// Should have light mode block
437+
expect(css).toContain('html[data-ainativekit-brand]');
438+
// Should have dark mode block
439+
expect(css).toContain('[data-theme="dark"]');
440+
});
441+
442+
it('should use same color for both modes when string value provided', () => {
443+
render(
444+
<ThemeProvider
445+
brandColors={{
446+
primary: '#6366F1', // Same for both modes
447+
}}
448+
>
449+
<div>Test</div>
450+
</ThemeProvider>
451+
);
452+
453+
const styleElement = document.querySelector('[data-ainativekit-brand-colors]');
454+
const css = styleElement?.textContent || '';
455+
456+
// Should appear in both light and dark blocks
457+
const matches = css.match(/#6366F1/g);
458+
expect(matches?.length).toBeGreaterThanOrEqual(2);
459+
});
460+
461+
it('should not duplicate warnings for string brand colors', () => {
462+
consoleWarnSpy.mockClear();
463+
464+
render(
465+
<ThemeProvider
466+
brandColors={{
467+
primary: '#AAAAAA', // Poor contrast - should warn only once
468+
}}
469+
>
470+
<div>Test</div>
471+
</ThemeProvider>
472+
);
473+
474+
// Count warnings about contrast for primary color
475+
const contrastWarnings = consoleWarnSpy.mock.calls.filter(
476+
(call) =>
477+
call[0].includes('primary') && call[0].includes('WCAG AA contrast requirements')
478+
);
479+
480+
// Should only warn once, not twice (once for light, once for dark)
481+
expect(contrastWarnings.length).toBe(1);
482+
});
483+
484+
it('should warn separately for different light/dark colors in object format', () => {
485+
consoleWarnSpy.mockClear();
486+
487+
render(
488+
<ThemeProvider
489+
brandColors={{
490+
primary: {
491+
light: '#AAAAAA', // Poor contrast in light mode
492+
dark: '#888888', // Poor contrast in dark mode (different color)
493+
},
494+
}}
495+
>
496+
<div>Test</div>
497+
</ThemeProvider>
498+
);
499+
500+
// Should warn twice - once for each different color
501+
const contrastWarnings = consoleWarnSpy.mock.calls.filter((call) =>
502+
call[0].includes('WCAG AA contrast requirements')
503+
);
504+
505+
expect(contrastWarnings.length).toBe(2);
506+
});
507+
508+
it('should support mixed string and object brand colors', () => {
509+
render(
510+
<ThemeProvider
511+
brandColors={{
512+
primary: '#6366F1', // String - same for both modes
513+
success: { light: '#059669', dark: '#34D399' }, // Object - different per mode
514+
warning: { light: '#D97706', dark: '#FBBF24' },
515+
error: '#DC2626', // String - same for both modes
516+
}}
517+
>
518+
<div>Test</div>
519+
</ThemeProvider>
520+
);
521+
522+
const styleElement = document.querySelector('[data-ainativekit-brand-colors]');
523+
const css = styleElement?.textContent || '';
524+
525+
// Check string values appear (used in both modes)
526+
expect(css).toContain('#6366F1');
527+
expect(css).toContain('#DC2626');
528+
529+
// Check object values appear
530+
expect(css).toContain('#059669'); // success light
531+
expect(css).toContain('#34D399'); // success dark
532+
expect(css).toContain('#D97706'); // warning light
533+
expect(css).toContain('#FBBF24'); // warning dark
534+
});
535+
536+
it('should apply correct hover/active mix colors per theme', () => {
537+
render(
538+
<ThemeProvider
539+
brandColors={{
540+
primary: { light: '#6366F1', dark: '#818CF8' },
541+
}}
542+
>
543+
<div>Test</div>
544+
</ThemeProvider>
545+
);
546+
547+
const styleElement = document.querySelector('[data-ainativekit-brand-colors]');
548+
const css = styleElement?.textContent || '';
549+
550+
// Light mode should mix with black
551+
expect(css).toContain('black');
552+
// Dark mode should mix with white
553+
expect(css).toContain('white');
554+
});
555+
});
556+
404557
describe('hex color normalization (end-to-end)', () => {
405558
it('should normalize colors without # prefix', () => {
406559
render(

0 commit comments

Comments
 (0)