Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add documentation for testing implementations #21

Closed
GriffinSauce opened this issue Jan 11, 2021 · 1 comment
Closed

Add documentation for testing implementations #21

GriffinSauce opened this issue Jan 11, 2021 · 1 comment
Labels
documentation Improvements or additions to documentation

Comments

@GriffinSauce
Copy link

Hi, thanks for this library! It was so easy and fast to implement exactly what I needed! 馃檶
Just wanted to share how I've started testing my implementations with React Testing Library:

First, create a custom render function that includes the theme provider with an optional value:

// test-utils.tsx
import React, { ReactElement } from 'react';
import { render, RenderOptions, RenderResult } from '@testing-library/react';
import { ThemeProvider } from 'next-themes';

interface TestProviderOptions {
  theme?: string;
}

interface CustomOptions extends RenderOptions, TestProviderOptions {}

const createTestProviders = ({
  theme = 'dark',
}: TestProviderOptions): React.FC => ({ children }) => (
  <ThemeProvider defaultTheme={theme} enableSystem={false} attribute="class">
    {children}
  </ThemeProvider>
);

const customRender = (
  ui: ReactElement,
  { theme, ...options }: CustomOptions = {},
): RenderResult =>
  render(ui, { wrapper: createTestProviders({ theme }), ...options });

// re-export everything
export * from '@testing-library/react';

// override render method
export { customRender as render };

Second: Add a test-id to select your select (馃榿):

// components/ThemeToggle.tsx
   <select
      className="font-semibold border border-gray-100 rounded"
      value={theme}
      data-testid="theme-select"
      onChange={handleChange}
    >
      <option value="light">Light Mode</option>
      <option value="dark">Dark Mode</option>
    </select>

Third: Test that the toggle actually changes the theme. Of course the exact implementation here will differ depending on how you write your toggle.

(technically you could assert the select value when you control it directly from the hook value, but I figured using a spy would be a bit more robust)

// components/ThemeToggle.test.tsx
import React from 'react';
import { useTheme } from 'next-themes';
import { render, fireEvent } from '../test/test-utils';
import ThemeToggle from './ThemeToggle';

const ThemeSpy: React.FC = () => {
  const { theme } = useTheme();
  return <span data-testid="theme-spy">{theme}</span>;
};

it('toggles the theme', async () => {
  const { getByTestId } = render(
    <>
      <ThemeToggle />
      <ThemeSpy />
    </>,
    { theme: 'dark' }, // Is also the default value, explicitly adding it here makes the test a bit more easy to read
  );
  const select = getByTestId('theme-select');
  const spy = getByTestId('theme-spy');

  fireEvent.change(select, { target: { value: 'light' } });

  expect(spy).toHaveTextContent('light');
});

Let me know if you see anything that could be improved of course!
I think it would be nice to add this as an example for the next person. :)

@pacocoursey pacocoursey added the documentation Improvements or additions to documentation label Jan 19, 2021
@RyanClementsHax
Copy link

Thanks for this! It was very helpful. I figured I would contribute additional points for how to integrate this with testing.

Perhaps since this issue was made, the repo changed, but when this solution is ran as is with the current code, jest will complain that matchMedia isn't a function and needs to be mocked.

The solution can actually be stolen from this repo lol. All one needs to do is add the following code to your setup file or test file as needed to mock out everything this library needs mocked:

let localStorageMock: { [key: string]: string } = {}

beforeAll(() => {
  // Create a mock of the window.matchMedia function
  global.matchMedia = jest.fn(query => ({
    matches: false,
    media: query,
    onchange: null,
    addListener: jest.fn(),
    removeListener: jest.fn(),
    addEventListener: jest.fn(),
    removeEventListener: jest.fn(),
    dispatchEvent: jest.fn()
  }))

  // Create mocks of localStorage getItem and setItem functions
  global.Storage.prototype.getItem = jest.fn(
    (key: string) => localStorageMock[key]
  )
  global.Storage.prototype.setItem = jest.fn((key: string, value: string) => {
    localStorageMock[key] = value
  })
})

beforeEach(() => {
  // Clear the localStorage-mock
  localStorageMock = {}
})

If you set resetMocks: true in your jest config like I do (ref to the docs for resetMocks), then you will need all of this in a before each since the mocks get wiped before each test

let localStorageMock: { [key: string]: string } = {}

beforeEach(() => {
  global.matchMedia = jest.fn(query => ({
    matches: false,
    media: query,
    onchange: null,
    addListener: jest.fn(),
    removeListener: jest.fn(),
    addEventListener: jest.fn(),
    removeEventListener: jest.fn(),
    dispatchEvent: jest.fn()
  }))

  global.Storage.prototype.getItem = jest.fn(
    (key: string) => localStorageMock[key]
  )
  global.Storage.prototype.setItem = jest.fn((key: string, value: string) => {
    localStorageMock[key] = value
  })

  localStorageMock = {}
})

Feel free to let me know if I missed anything

@trm217 trm217 closed this as completed May 9, 2024
@trm217 trm217 closed this as not planned Won't fix, can't repro, duplicate, stale May 9, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Improvements or additions to documentation
Projects
None yet
Development

No branches or pull requests

4 participants