storybook-theme.test.tsx 3.0 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485
  1. import { fireEvent, render } from '@testing-library/react';
  2. import { afterEach, expect, test } from 'vitest';
  3. import { withTheme } from '../../.storybook/preview';
  4. import { ThemeProvider, useTheme } from '@/hooks/useTheme';
  5. function Story() {
  6. return <div>Story</div>;
  7. }
  8. function StorybookTheme({ theme }: { theme: 'light' | 'dark' }) {
  9. return withTheme(Story, { globals: { theme } } as Partial<
  10. Parameters<typeof withTheme>[1]
  11. > as Parameters<typeof withTheme>[1]);
  12. }
  13. function ThemeToggle() {
  14. const { toggleTheme } = useTheme();
  15. return <button onClick={toggleTheme}>toggle</button>;
  16. }
  17. afterEach(() => {
  18. document.body.className = '';
  19. document.documentElement.removeAttribute('data-theme');
  20. document.documentElement.style.colorScheme = '';
  21. });
  22. // Without color-scheme the browser paints native scrollbars light inside dark
  23. // modals, e.g. the Edit Client body.
  24. test('native scrollbars follow the panel theme', () => {
  25. const { getByRole } = render(
  26. <ThemeProvider>
  27. <ThemeToggle />
  28. </ThemeProvider>,
  29. );
  30. expect(document.documentElement.style.colorScheme).toBe('dark');
  31. fireEvent.click(getByRole('button'));
  32. expect(document.documentElement.style.colorScheme).toBe('light');
  33. });
  34. test('native scrollbars follow the Storybook theme', () => {
  35. const { rerender } = render(<StorybookTheme theme="light" />);
  36. expect(document.documentElement.style.colorScheme).toBe('light');
  37. rerender(<StorybookTheme theme="dark" />);
  38. expect(document.documentElement.style.colorScheme).toBe('dark');
  39. });
  40. test('preserves unrelated body classes when applying the Storybook theme', () => {
  41. document.body.className = 'storybook-fixture dark';
  42. document.documentElement.setAttribute('data-theme', 'ultra-dark');
  43. const { rerender } = render(<StorybookTheme theme="light" />);
  44. expect(document.body.classList.contains('storybook-fixture')).toBe(true);
  45. expect(document.body.classList.contains('light')).toBe(true);
  46. expect(document.body.classList.contains('dark')).toBe(false);
  47. expect(document.documentElement.hasAttribute('data-theme')).toBe(false);
  48. rerender(<StorybookTheme theme="dark" />);
  49. expect(document.body.classList.contains('storybook-fixture')).toBe(true);
  50. expect(document.body.classList.contains('dark')).toBe(true);
  51. expect(document.body.classList.contains('light')).toBe(false);
  52. });
  53. test('preserves unrelated body classes when applying the panel theme', () => {
  54. document.body.className = 'panel-fixture';
  55. const message = document.createElement('div');
  56. message.id = 'message';
  57. message.className = 'message-fixture';
  58. document.body.append(message);
  59. render(
  60. <ThemeProvider>
  61. <div>Panel</div>
  62. </ThemeProvider>,
  63. );
  64. expect(document.body.classList.contains('panel-fixture')).toBe(true);
  65. expect(document.body.classList.contains('dark')).toBe(true);
  66. expect(document.body.classList.contains('light')).toBe(false);
  67. expect(message.classList.contains('message-fixture')).toBe(true);
  68. expect(message.classList.contains('dark')).toBe(true);
  69. });