Jest: Cannot read property 'secondary' of undefined - after MUI upgrade from V4 toV5

Viewed 920

We upgraded MUI from v4 to v5 and we have UI tests which started failing. Error is:

TypeError: Cannot read property 'secondary' of undefined (I added comment to which line in code this refers)

Test example:

     describe('<AnonDragNDropFileUpload />', () => {
      it('should render', () => {
        const blob = () => {
          return new File(['Test string'], 'Test file.txt');
        };
        const fileSet: AnonFileSet = {
          originalFile: { get: blob, set: () => undefined },
          compressedFile: { get: () => undefined, set: () => undefined },
        };
        const result = render(<AnonDragNDropFileUpload fileSet={fileSet} downloadFileAction={jest.fn()} clearFileAction={jest.fn()} />);
        expect(result).toBeTruthy();
      });
    });

Code:

import { Paper } from '@mui/material';
import { green, red } from '@mui/material/colors';
import { lighten, Theme } from '@mui/material/styles';
import makeStyles from '@mui/styles/makeStyles';
import { JobInputFileTypeEnum } from 'app/api';
import React, { useCallback, useEffect, useState } from 'react';
import { useDropzone } from 'react-dropzone';
import { AnonFileSet } from '.';

const useDropZoneStyles = makeStyles((theme: Theme) => ({
  dragndropZone: {
    backgroundColor: lighten(theme.palette.secondary.light, 0.8), // <-- this line fails
    width: '100%',
  },
  info: {
    backgroundColor: green[100],
    width: '100%',
  },
}));

interface Props {
  fileSet: AnonFileSet;
  clearFileAction: (fileType?: JobInputFileTypeEnum) => void;
  downloadFileAction: () => void;
}

export const AnonDragNDropFileUpload: React.FC<Props> = ({ fileSet, clearFileAction, downloadFileAction }) => {
  const classes = useDropZoneStyles();
  const [fileLabel, setFileLabel] = useState('');

  const onDrop = useCallback(async (acceptedFiles: File[]) => {
    setFileLabel(fileSet.originalFile.get()?.name ?? '');
    fileSet.originalFile.set(acceptedFiles[0]);
  }, []);

  const { acceptedFiles, getRootProps, getInputProps } = useDropzone({ onDrop, multiple: false, accept: '.csv' });
  const { ref, ...rootProps } = getRootProps();

  const handleDeleteFile = () => {
    acceptedFiles.splice(
      acceptedFiles.findIndex((x) => x.name === fileSet.originalFile.get()?.name),
      1,
    );
    clearFileAction();
  };

  useEffect(() => {
    setFileLabel(fileSet.originalFile.get()?.name ?? '');
  }, [fileSet.originalFile.get()]);

  if (fileSet.originalFile.get())
    return (
      <Paper variant="outlined">
        <div className="flex px-8 py-32 justify-center">
          <div className="flex">
            <a style={{ color: '#888888', textDecoration: 'underline', cursor: 'default' }}>{fileLabel}</a>
            <p className="mx-4">&nbsp;</p>
            <a onClick={handleDeleteFile} style={{ color: red[600], cursor: 'pointer' }} role="link">
              {'[Clear File]'}
            </a>
            <p className="mx-4">&nbsp;</p>
            {fileSet.compressedFile?.get() && (
              <a onClick={downloadFileAction} style={{ color: green[600], cursor: 'pointer' }} role="link">
                {'[Download File]'}
              </a>
            )}
          </div>
        </div>
      </Paper>
    );

  return (
    <Paper {...rootProps} className={classes.dragndropZone} variant="outlined">
      <div className="flex px-8 py-32 justify-center">
        <input {...getInputProps()} name="customerCSVFilename" placeholder="CSV File"/>
        <p>{fileLabel}</p>
      </div>
    </Paper>
  );
};

What I've tried so far:

  • Checked if ThemeProvider is available
  • Added custom theme just to the code block which fails

All other tests which are testing hooks or custom logic (like pure TypeScript) are working without any issues, but it seems that somehow using styles from MUI is not working. When I remove these lines, test is passing, so my guess it has something with MUI makeStyles. Any ideas? Thanks for helping me out.

1 Answers

Try to use a mocked component, wrapped inside a ThemeProvider instance:

import theme from './path/to/your/theme'

const MockAnonDragNDropFileUpload = (props: any) => {
    return (
        <ThemeProvider theme={theme}>
            <AnonDragNDropFileUpload {...props} />
        </ThemeProvider>
    );
}

To mock the component using the existing theme you could separate its declaration into a distinct file:

const theme = createTheme({
    ...
});

export default theme;

Then use the mocked instance in the tests:

describe('<AnonDragNDropFileUpload />', () => {
  it('should render', () => {
    ...
    const result = render(
      <MockAnonDragNDropFileUpload
        fileSet={fileSet}
        downloadFileAction={jest.fn()}
        clearFileAction={jest.fn()}
      />
    );
    expect(result).toBeTruthy();
  });
});
Related