首页 > 解决方案 > Jest / RTL Failure -- MUI V5 + Emotion Theme 问题

问题描述

我目前正在使用 MUI V5 和 Emotion 进行造型。下面的代码在 Storybook 中没有问题,所以我有点困惑为什么主题没有被注入到 Emotion 样式中。我认为 ThemeProvider 会处理注入,但它似乎不起作用。有什么想法吗?蒂亚!

Badge.tsx

import React, { ReactElement } from 'react';

import * as S from './styles';

export type TBadgeEmphasis = 'subtle' | 'high';
export type TBadgeType = 'default' | 'success' | 'warning' | 'error';

export interface BadgeProps {
  /**
   * Text for the badge.
   */
  children: ReactElement | string;

  /**
   * Option here between `subtle` and `high` depending on the desired intensity.
   */
  emphasis: TBadgeEmphasis;

  /**
   * Option here between `default`, `success`, `warning` and `error`.
   */
  type: string;
}

export const Badge = (props: BadgeProps): ReactElement => {
  return (
    <S.StyledBadge
      data-testid={`${props.type}-${props.emphasis}-badge`}
      emphasis={props.emphasis}
      label={props.children}
      type={props.type}
    />
  );
};

Badge.styles.tsx

import { css } from '@emotion/react';
import { styled } from '@mui/material/styles';
import { Chip as MuiChip, ChipProps as MuiChipProps } from '@mui/material';

interface BadgeStyleProps extends MuiChipProps {
  emphasis: string;
  type: string;
}

const buildStyles = (props: BadgeStyleProps) => {
  let styles;

  const { emphasis, theme, type } = props;

  const baseStyle = css`
    font-size: 13px;
    font-weight: 700;
    border-radius: 4px;
    min-width: 90px;
    max-width: 180px;
    padding-left: 10px;
    padding-right: 10px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  `;

  if (emphasis === 'subtle') {
    switch (type) {
      case 'default':
        styles = css`
          ${baseStyle};
          background: ${theme.palette.expanded.coolGrey2};
          color: ${theme.palette.expanded.slate};
        `;
        break;
      case 'error':
        styles = css`
          ${baseStyle};
          background: ${theme.palette.expanded.red4};
          color: ${theme.palette.expanded.red2};
        `;
        break;
      case 'success':
        styles = css`
          ${baseStyle};
          background: ${theme.palette.expanded.green4};
          color: ${theme.palette.expanded.green1};
        `;
        break;
      case 'warning':
        styles = css`
          ${baseStyle};
          background: ${theme.palette.expanded.orange3};
          color: ${theme.palette.expanded.orange1};
        `;
        break;
    }
  } else if (emphasis === 'high') {
    switch (type) {
      case 'default':
        styles = css`
          ${baseStyle};
          background: ${theme.palette.expanded.lightSlate};
          color: ${theme.palette.common.white};
        `;
        break;
      case 'error':
        styles = css`
          ${baseStyle};
          background: ${theme.palette.expanded.coral};
          color: ${theme.palette.common.white};
        `;
        break;
      case 'success':
        styles = css`
          ${baseStyle};
          background: ${theme.palette.expanded.green1};
          color: ${theme.palette.common.white};
        `;
        break;
      case 'warning':
        styles = css`
          ${baseStyle};
          background: ${theme.palette.expanded.orange1};
          color: ${theme.palette.common.white};
        `;
        break;
    }
  }

  return styles;
};

export const StyledBadge = styled(MuiChip)`
  ${buildStyles};
`;

Badge.test.tsx

import { ThemeProvider } from '@mui/material/styles';
import { render, screen } from '@testing-library/react';
import React, { ReactNode } from 'react';

import { Badge, BadgeProps } from '../';
import { theme } from '../../../packages/core/src/theme';

const DefaultSubtleArgs: BadgeProps = {
  children: 'Default',
  emphasis: 'subtle',
  type: 'default',
};

const ThemeWrapper = ({ children }: { children: any }) => {
  return <ThemeProvider theme={theme}>{children}</ThemeProvider>;
};

test('renders the badge in the subtle default state', () => {
  render(
    <ThemeWrapper>
      <Badge {...DefaultSubtleArgs} />
    </ThemeWrapper>
  );
  expect(screen.getByTestId('default-subtle-badge')).toHaveTextContent(
    'Default'
  );
});

const SuccessSubtleArgs: BadgeProps = {
  children: 'Success',
  emphasis: 'subtle',
  type: 'success',
};

test('renders the badge in the subtle success state', () => {
  render(
    <ThemeWrapper>
      <Badge {...SuccessSubtleArgs} />
    </ThemeWrapper>
  );
  expect(screen.getByTestId('success-subtle-badge')).toHaveTextContent(
    'Success'
  );
});

const WarningSubtleArgs: BadgeProps = {
  children: 'Warning',
  emphasis: 'subtle',
  type: 'warning',
};

test('renders the badge in the subtle warning state', () => {
  render(
    <ThemeWrapper>
      <Badge {...WarningSubtleArgs} />
    </ThemeWrapper>
  );
  expect(screen.getByTestId('warning-subtle-badge')).toHaveTextContent(
    'Warning'
  );
});

const ErrorSubtleArgs: BadgeProps = {
  children: 'Error',
  emphasis: 'subtle',
  type: 'error',
};

test('renders the badge in the subtle error state', () => {
  render(
    <ThemeWrapper>
      <Badge {...ErrorSubtleArgs} />
    </ThemeWrapper>
  );
  expect(screen.getByTestId('error-subtle-badge')).toHaveTextContent('Error');
});

标签: reactjsmaterial-ui

解决方案


MUI V5 与 Theme Providers 一起工作的方式肯定会发生一些事情。

我找到了一种解决方法,但它并不优雅。请让我知道其他人有另一种方式。

通过您的测试,您只需坚持使用 包装每个组件ThemeProvider并注入您的主题。

在实际的组件文件中,您将需要使用useTheme钩子来获取正确的主题对象,然后您需要将其作为唯一的道具传递给样式化的组件。

import { css } from '@emotion/react';
import { Chip as MuiChip, ChipProps as MuiChipProps } from '@mui/material';
import { styled, useTheme, Theme } from '@mui/material/styles';
import React, { ReactElement } from 'react';

export type TBadgeEmphasis = 'subtle' | 'high';
export type TBadgeType = 'default' | 'success' | 'warning' | 'error';

export interface BadgeProps {
  /**
   * Text for the badge.
   */
  children: ReactElement | string;

  /**
   * Option here between `subtle` and `high` depending on the desired intensity.
   */
  emphasis: TBadgeEmphasis;

  /**
   * Option here between `default`, `success`, `warning` and `error`.
   */
  type: string;
}

interface BadgeStyleProps extends MuiChipProps {
  emphasis: string;
  theme: Theme;
  type: string;
}
const StyledBadge = styled(MuiChip)<BadgeStyleProps>(
  ({ emphasis, theme, type }) => {
    let styles;

    const baseStyle = css`
      font-size: 13px;
      font-weight: 700;
      border-radius: 4px;
      min-width: 90px;
      max-width: 180px;
      padding-left: 10px;
      padding-right: 10px;
      white-space: nowrap;
      overflow: hidden;
      text-overflow: ellipsis;
    `;

    if (emphasis === 'subtle') {
      switch (type) {
        case 'default':
          styles = css`
            ${baseStyle};
            background: ${theme.palette.expanded.coolGrey2};
            color: ${theme.palette.expanded.slate};
          `;
          break;
        case 'error':
          styles = css`
            ${baseStyle};
            background: ${theme.palette.expanded.red4};
            color: ${theme.palette.expanded.red2};
          `;
          break;
        case 'success':
          styles = css`
            ${baseStyle};
            background: ${theme.palette.expanded.green4};
            color: ${theme.palette.expanded.green1};
          `;
          break;
        case 'warning':
          styles = css`
            ${baseStyle};
            background: ${theme.palette.expanded.orange3};
            color: ${theme.palette.expanded.orange1};
          `;
          break;
      }
    } else if (emphasis === 'high') {
      switch (type) {
        case 'default':
          styles = css`
            ${baseStyle};
            background: ${theme.palette.expanded.lightSlate};
            color: ${theme.palette.common.white};
          `;
          break;
        case 'error':
          styles = css`
            ${baseStyle};
            background: ${theme.palette.expanded.coral};
            color: ${theme.palette.common.white};
          `;
          break;
        case 'success':
          styles = css`
            ${baseStyle};
            background: ${theme.palette.expanded.green1};
            color: ${theme.palette.common.white};
          `;
          break;
        case 'warning':
          styles = css`
            ${baseStyle};
            background: ${theme.palette.expanded.orange1};
            color: ${theme.palette.common.white};
          `;
          break;
      }
    }

    return styles;
  }
);

export const Badge = (props: BadgeProps): ReactElement => {
  const themeOverride = useTheme();

  return (
    <StyledBadge
      data-testid={`${props.type}-${props.emphasis}-badge`}
      emphasis={props.emphasis}
      label={props.children}
      theme={themeOverride}
      type={props.type}
    />
  );
};


推荐阅读