Skip to content

Testing ​

React parts are tested with Jest and Testing Library, run npm test.

The configuration is shared and lives in the base theme (jest.base.cjs): a subtheme has no jest.config.cjs, no Babel config and no test dependencies. npm test runs frame test, extra arguments go to Jest: npm test -- --watch, npm test -- -u.

What the shared config does
jsdom environmentresolved from the base theme
Babel (preset-env, preset-react classic)presets resolved from the base theme, so no .babelrc is needed
Components/... aliasmaps to <theme>/components
CSS/SCSS importsmocked
One Reactreact / react-dom always resolve to the base theme copy, even if a dependency (rsuite) installed another
@testing-library/*importable from the base theme location
setup filejest-dom matchers, global React, stubs for Drupal.t and drupalSettings, matchMedia

To override something add a jest.config.cjs to the theme, it is used instead: module.exports = require('<base theme>/jest.base.cjs')(__dirname, { testPathIgnorePatterns: [...] }). The base theme's own tests (src/__tests__) cover apiClient and executeWhenVisible.

Example ​

Components rely on globals that Drupal provides (Drupal, apiClient), so stub them in beforeEach:

jsx
import { RecipeExplorer } from '../RecipeExplorer';
import { render, screen, waitFor, fireEvent } from '@testing-library/react';

beforeEach(() => {
  window.Drupal = {
    t: (s, args = {}) => Object.entries(args).reduce((r, [k, v]) => r.replace(k, v), s),
    formatPlural: (n, one, many) => (n === 1 ? one : many.replace('@count', n)),
  };
  window.apiClient = jest.fn(() => Promise.resolve(response([recipe])));
  global.apiClient = window.apiClient;
});

it('requests the endpoint again when a filter changes', async () => {
  render(<RecipeExplorer endpoint="/api/recipes" heading="Recipes" />);
  await screen.findByText('Soup');
  fireEvent.change(screen.getByLabelText('Difficulty'), { target: { value: 'hard' } });
  await waitFor(() => expect(apiClient).toHaveBeenLastCalledWith('/api/recipes', { difficulty: 'hard' }));
});

Test the React component (RecipeExplorer.jsx), not index.jsx: the behavior wrapper only wires Drupal to React.

What is not covered ​

  • Drupal-side rendering (SDC props validation, library attachment) needs a running site or core's ComponentKernelTestBase.
  • The demo recorder (npm run demo) doubles as a smoke test: it fails with exit code 1 if the browser logs a console error or page error.