import * as React from 'react'; import { expect } from 'chai'; import { spy } from 'sinon'; import { createRenderer, screen } from '@mui/internal-test-utils'; import { ThemeContext } from '@mui/styled-engine'; import * as material from '@mui/material'; import * as joy from '@mui/joy'; // simulate 3rd-party library like Theme-UI, Chakra-UI, or Mantine interface LibTheme { palette: { brand: string }; vars: { palette: { brand: string } }; } function LibThemeProvider({ children }: React.PropsWithChildren<{}>) { const theme = React.useMemo( () => ({ palette: { brand: '#ff5252' }, vars: { palette: { brand: 'var(--palette-brand)' } } }), [], ); return {children}; } function LibComponent() { const theme = React.useContext(ThemeContext as unknown as React.Context); return
; } const joyTheme = joy.extendTheme({ components: { JoyButton: { defaultProps: { variant: 'outlined', }, styleOverrides: { root: ({ theme }) => ({ color: theme.vars.palette.text.primary, mixBlendMode: 'darken', }), }, }, }, }); const CustomJoy = joy.styled('div')(({ theme }) => ({ fontSize: theme.vars.fontSize.md, })); const materialTheme = material.createTheme({ components: { MuiButton: { defaultProps: { variant: 'outlined', }, styleOverrides: { root: ({ theme }) => ({ color: theme.palette.text.primary, mixBlendMode: 'darken', }), }, }, }, }); const CustomMaterial = material.styled('div')(({ theme }) => ({ borderRadius: theme.shape.borderRadius, })); describe('Multiple nested theme providers', () => { const { render } = createRenderer(); let originalMatchmedia: any; let storage: Record = {}; const createMatchMedia = (matches: boolean) => () => ({ matches, // Keep mocking legacy methods because @mui/material v5 still uses them addListener: () => {}, addEventListener: () => {}, removeListener: () => {}, removeEventListener: () => {}, }); beforeEach(() => { originalMatchmedia = window.matchMedia; // Create mocks of localStorage getItem and setItem functions Object.defineProperty(globalThis, 'localStorage', { value: { getItem: spy((key) => storage[key]), setItem: spy((key, value) => { storage[key] = value; }), }, configurable: true, }); // clear the localstorage storage = {}; window.matchMedia = createMatchMedia(false) as unknown as typeof window.matchMedia; }); afterEach(() => { window.matchMedia = originalMatchmedia; }); it('[docs] Material UI + Joy UI', () => { render( ({ // test `sx` bgcolor: theme.vars.palette.neutral[100], })} > Joy ({ bgcolor: theme.palette.secondary.light, })} > Material , ); // these test if `useThemeProps` works with theme scoping expect(screen.getByText('Joy')).to.have.class(joy.buttonClasses.variantOutlined); expect(screen.getByText('Joy')).toHaveComputedStyle({ mixBlendMode: 'darken' }); expect(screen.getByText('Material')).to.have.class(material.buttonClasses.outlinedPrimary); expect(screen.getByText('Material')).toHaveComputedStyle({ mixBlendMode: 'darken' }); }); it('Material UI works with 3rd-party lib', () => { render( Material {/* styled() should work with theme scoping */} {' '} {/* still able to render even though it is wrapped in Material UI ThemeProvider */} , ); expect(screen.getByText('Material')).to.have.class(material.buttonClasses.outlinedPrimary); }); it('Joy UI works with 3rd-party lib', () => { render( Joy {/* styled() should work with theme scoping */} {' '} {/* still able to render even though it is wrapped in Material UI ThemeProvider */} , ); expect(screen.getByText('Joy')).to.have.class(joy.buttonClasses.variantOutlined); }); });