diff --git a/__tests__/resolveConfig.test.js b/__tests__/resolveConfig.test.js index 7ef2cbbe39e1..d80eb0e91194 100644 --- a/__tests__/resolveConfig.test.js +++ b/__tests__/resolveConfig.test.js @@ -430,3 +430,274 @@ test('functions in the user theme section are lazily evaluated', () => { }, }) }) + +test('theme values in the extend section extend the existing theme', () => { + const userConfig = { + theme: { + extend: { + opacity: { + '25': '25', + '75': '.75', + }, + backgroundColors: { + customBackground: '#bada55', + }, + }, + }, + } + + const defaultConfig = { + prefix: '-', + important: false, + separator: ':', + theme: { + colors: { + cyan: 'cyan', + magenta: 'magenta', + yellow: 'yellow', + }, + opacity: { + '0': '0', + '50': '.5', + '100': '1', + }, + backgroundColors: ({ colors }) => colors, + }, + variants: { + backgroundColors: ['responsive', 'hover', 'focus'], + opacity: ['responsive', 'hover', 'focus'], + }, + } + + const result = resolveConfig([userConfig, defaultConfig]) + + expect(result).toEqual({ + prefix: '-', + important: false, + separator: ':', + theme: { + colors: { + cyan: 'cyan', + magenta: 'magenta', + yellow: 'yellow', + }, + opacity: { + '0': '0', + '50': '.5', + '100': '1', + '25': '25', + '75': '.75', + }, + backgroundColors: { + cyan: 'cyan', + magenta: 'magenta', + yellow: 'yellow', + customBackground: '#bada55', + }, + }, + variants: { + backgroundColors: ['responsive', 'hover', 'focus'], + opacity: ['responsive', 'hover', 'focus'], + }, + }) +}) + +test('theme values in the extend section extend the user theme', () => { + const userConfig = { + theme: { + opacity: { + '0': '0', + '20': '.2', + '40': '.4', + }, + height: theme => theme.width, + extend: { + opacity: { + '60': '.6', + '80': '.8', + '100': '1', + }, + height: { + customHeight: '500vh', + }, + }, + }, + } + + const defaultConfig = { + prefix: '-', + important: false, + separator: ':', + theme: { + opacity: { + '0': '0', + '50': '.5', + '100': '1', + }, + height: { + '0': 0, + full: '100%', + }, + width: { + '0': 0, + '1': '.25rem', + '2': '.5rem', + '3': '.75rem', + '4': '1rem', + }, + }, + variants: { + opacity: ['responsive', 'hover', 'focus'], + height: ['responsive'], + width: ['responsive'], + }, + } + + const result = resolveConfig([userConfig, defaultConfig]) + + expect(result).toEqual({ + prefix: '-', + important: false, + separator: ':', + theme: { + opacity: { + '0': '0', + '20': '.2', + '40': '.4', + '60': '.6', + '80': '.8', + '100': '1', + }, + height: { + '0': 0, + '1': '.25rem', + '2': '.5rem', + '3': '.75rem', + '4': '1rem', + customHeight: '500vh', + }, + width: { + '0': 0, + '1': '.25rem', + '2': '.5rem', + '3': '.75rem', + '4': '1rem', + }, + }, + variants: { + opacity: ['responsive', 'hover', 'focus'], + height: ['responsive'], + width: ['responsive'], + }, + }) +}) + +test('theme values in the extend section can extend values that are depended on lazily', () => { + const userConfig = { + theme: { + extend: { + colors: { + red: 'red', + green: 'green', + blue: 'blue', + }, + backgroundColors: { + customBackground: '#bada55', + }, + }, + }, + } + + const defaultConfig = { + prefix: '-', + important: false, + separator: ':', + theme: { + colors: { + cyan: 'cyan', + magenta: 'magenta', + yellow: 'yellow', + }, + backgroundColors: ({ colors }) => colors, + }, + variants: { + backgroundColors: ['responsive', 'hover', 'focus'], + }, + } + + const result = resolveConfig([userConfig, defaultConfig]) + + expect(result).toEqual({ + prefix: '-', + important: false, + separator: ':', + theme: { + colors: { + cyan: 'cyan', + magenta: 'magenta', + yellow: 'yellow', + red: 'red', + green: 'green', + blue: 'blue', + }, + backgroundColors: { + cyan: 'cyan', + magenta: 'magenta', + yellow: 'yellow', + red: 'red', + green: 'green', + blue: 'blue', + customBackground: '#bada55', + }, + }, + variants: { + backgroundColors: ['responsive', 'hover', 'focus'], + }, + }) +}) + +test('theme values in the extend section are not deeply merged', () => { + const userConfig = { + theme: { + extend: { + fonts: { + sans: ['Comic Sans'], + }, + }, + }, + } + + const defaultConfig = { + prefix: '-', + important: false, + separator: ':', + theme: { + fonts: { + sans: ['system-ui', 'Helvetica Neue', 'sans-serif'], + serif: ['Constantia', 'Georgia', 'serif'], + mono: ['Menlo', 'Courier New', 'monospace'], + }, + }, + variants: { + fonts: ['responsive'], + }, + } + + const result = resolveConfig([userConfig, defaultConfig]) + + expect(result).toEqual({ + prefix: '-', + important: false, + separator: ':', + theme: { + fonts: { + sans: ['Comic Sans'], + serif: ['Constantia', 'Georgia', 'serif'], + mono: ['Menlo', 'Courier New', 'monospace'], + }, + }, + variants: { + fonts: ['responsive'], + }, + }) +}) diff --git a/src/util/resolveConfig.js b/src/util/resolveConfig.js index 2f16773070f4..6189d6cdca85 100644 --- a/src/util/resolveConfig.js +++ b/src/util/resolveConfig.js @@ -1,19 +1,36 @@ -import _ from 'lodash' +import mergeWith from 'lodash/mergeWith' +import isFunction from 'lodash/isFunction' +import defaults from 'lodash/defaults' +import map from 'lodash/map' function resolveFunctionKeys(object) { return Object.keys(object).reduce((resolved, key) => { return { ...resolved, - [key]: _.isFunction(object[key]) ? object[key](object) : object[key], + [key]: isFunction(object[key]) ? object[key](object) : object[key], } }, {}) } +function mergeExtensions({ extend, ...theme }) { + return mergeWith({}, theme, extend, (_, extensions, key) => { + return isFunction(theme[key]) + ? mergedTheme => ({ + ...theme[key](mergedTheme), + ...extensions, + }) + : { + ...theme[key], + ...extensions, + } + }) +} + export default function(configs) { - return _.defaults( + return defaults( { - theme: resolveFunctionKeys(_.defaults(..._.map(configs, 'theme'))), - variants: _.defaults(..._.map(configs, 'variants')), + theme: resolveFunctionKeys(mergeExtensions(defaults(...map(configs, 'theme')))), + variants: defaults(...map(configs, 'variants')), }, ...configs )