/** * @jest-environment jsdom * @jsxImportSource react */ import React from 'react' import { cleanup } from '@testing-library/react' import { renderJSON } from '@theme-ui/test-utils' import { matchers } from '@emotion/jest' import mockConsole from 'jest-mock-console' import { jsx, __ThemeUIContext, useThemeUI, merge, ThemeProvider, ThemeUIContextValue, Theme, } from '../src' afterEach(cleanup) expect.extend(matchers) describe('ThemeProvider', () => { test('renders', () => { const json = renderJSON(

Hello

) expect(json).toMatchSnapshot() }) test('warns when multiple versions of emotion are installed', () => { const restore = mockConsole() const _ = renderJSON( jsx(__ThemeUIContext.Provider, { value: { __EMOTION_VERSION__: '9.0.0', theme: {}, }, children: ( Conflicting versions ), }) ) expect(console.warn).toBeCalled() restore() }) test('functional themes receive outer theme', () => { const outer = { colors: { text: 'tomato', }, } const theme = jest.fn(() => ({})) const json = renderJSON( jsx( ThemeProvider, { theme: outer }, jsx( ThemeProvider, { theme }, jsx('div', { sx: { color: 'text', }, }) ) ) ) expect(theme).toHaveBeenCalledWith(outer) expect(json).toHaveStyleRule('color', 'text') }) test('functional themes can be used at the top level', () => { const theme = jest.fn(() => ({ config: { useCustomProperties: false, }, colors: { primary: 'tomato', }, })) let json expect(() => { json = renderJSON( jsx( ThemeProvider, { theme }, jsx( 'div', { sx: { color: 'primary', }, }, 'hi' ) ) ) }).not.toThrow() expect(json).toHaveStyleRule('color', 'tomato') }) test('variants support functional values', () => { const theme: Theme = { colors: { primary: 'tomato', }, cards: { default: { border: (t) => `1px solid ${t.colors!.primary}`, }, }, } const json = renderJSON( jsx( ThemeProvider, { theme }, jsx('div', { sx: { variant: 'cards.default', }, }) ) ) expect(json).toHaveStyleRule('border', '1px solid tomato') }) }) describe('jsx', () => { test('custom pragma adds styles', () => { const json = renderJSON( jsx('div', { sx: { mx: 'auto', p: 2, bg: 'tomato', }, }) ) expect(json).toHaveStyleRule('margin-left', 'auto') expect(json).toHaveStyleRule('margin-right', 'auto') expect(json).toHaveStyleRule('padding', '8px') expect(json).toHaveStyleRule('background-color', 'tomato') }) test('adds raw values with css prop', () => { const json = renderJSON( jsx('div', { css: { margin: 4, }, }) ) expect(json).toHaveStyleRule('margin', '4px') }) test('css prop accepts functions', () => { const json = renderJSON( jsx( ThemeProvider, { theme: { colors: { primary: 'tomato', }, }, }, jsx('div', { css: (t: Theme) => ({ color: t.colors?.primary, }), }) ) ) expect(json).toHaveStyleRule('color', 'tomato') }) test('sx and css prop can be used together', () => { const json = renderJSON( jsx('div', { css: { margin: 0, }, sx: { bg: 'tomato', }, }) ) expect(json).toHaveStyleRule('background-color', 'tomato') expect(json).toHaveStyleRule('margin', '0') }) test('custom pragma handles null props', () => { const json = renderJSON(jsx('h1', null, 'hello')) expect(json).toMatchSnapshot() }) test('sx prop supports dot notation', () => { const json = renderJSON( jsx( ThemeProvider, { theme: { config: { useCustomProperties: false, }, colors: { text: 'black', base: { blue: ['#07c'], primary: 'cyan', }, }, }, }, jsx('div', { sx: { color: 'base.blue.0', backgroundColor: 'base.primary', }, }) ) ) expect(json).toHaveStyleRule('background-color', 'cyan') expect(json).toHaveStyleRule('color', '#07c') }) test('does not add css prop when not provided', () => { jest.spyOn(global.console, 'warn') const json = renderJSON(jsx(React.Fragment, null, 'hi')) expect(json?.props).toEqual(undefined) expect(console.warn).not.toBeCalled() }) }) describe('merge', () => { test('deeply merges objects', () => { const result = merge( { // @ts-ignore beep: 'boop', hello: { hi: 'howdy', }, }, { bleep: 'bloop', hello: { ohaiyo: 'osu', }, } ) expect(result).toEqual({ beep: 'boop', bleep: 'bloop', hello: { hi: 'howdy', ohaiyo: 'osu', }, }) }) test('merges multiple objects', () => { const result = merge.all( { beep: 'boop', }, { bleep: 'bloop', }, { plip: 'plop', } ) expect(result).toEqual({ beep: 'boop', bleep: 'bloop', plip: 'plop', }) }) test('does not attempt to merge React components', () => { const h1 = React.forwardRef((props, ref) => ( // eslint-disable-next-line jsx-a11y/heading-has-content

)) const result = (merge as any)( { // eslint-disable-next-line jsx-a11y/heading-has-content h1: (props: {}) =>

, }, { h1, } ) expect(result).toEqual({ h1 }) }) test('primitive types override arrays', () => { const result = merge( { fontSizes: [3, 4, 5], }, { fontSizes: 4 as any, } ) expect(result).toEqual({ fontSizes: 4, }) }) test('arrays override arrays', () => { const result = merge( { fontSizes: [3, 4, 5], }, { fontSizes: [6, 7], } ) expect(result).toEqual({ fontSizes: [6, 7], }) }) test('arrays override primitive types', () => { const result = merge( { fontSizes: 5 as any, }, { fontSizes: [6, 7], } ) expect(result).toEqual({ fontSizes: [6, 7], }) }) }) describe('useThemeUI', () => { test('returns theme context', () => { let context: ThemeUIContextValue | undefined const GetContext = () => { context = useThemeUI() return null } renderJSON( ) expect(context).toBeTruthy() expect(context?.theme.colors?.text).toBe('tomato') }) })