import { corePluginList } from '../src/corePluginList' import resolveConfig from '../src/util/resolveConfig' test('prefix key overrides default prefix', () => { const userConfig = { prefix: 'tw-', } const defaultConfig = { prefix: '', important: false, separator: ':', theme: { screens: { mobile: '400px', }, }, variants: { appearance: ['responsive'], borderCollapse: [], borderColors: ['responsive', 'hover', 'focus'], }, } const result = resolveConfig([userConfig, defaultConfig]) expect(result).toMatchObject({ prefix: 'tw-', important: false, separator: ':', theme: { screens: { mobile: '400px', }, }, variants: { appearance: ['responsive'], borderCollapse: [], borderColors: ['responsive', 'hover', 'focus'], }, }) }) test('important key overrides default important', () => { const userConfig = { important: true, } const defaultConfig = { prefix: '', important: false, separator: ':', theme: { screens: { mobile: '400px', }, }, variants: { appearance: ['responsive'], borderCollapse: [], borderColors: ['responsive', 'hover', 'focus'], }, } const result = resolveConfig([userConfig, defaultConfig]) expect(result).toMatchObject({ prefix: '', important: true, separator: ':', theme: { screens: { mobile: '400px', }, }, variants: { appearance: ['responsive'], borderCollapse: [], borderColors: ['responsive', 'hover', 'focus'], }, }) }) test('important (selector) key overrides default important', () => { const userConfig = { important: '#app', } const defaultConfig = { prefix: '', important: false, separator: ':', theme: { screens: { mobile: '400px', }, }, variants: { appearance: ['responsive'], borderCollapse: [], borderColors: ['responsive', 'hover', 'focus'], }, } const result = resolveConfig([userConfig, defaultConfig]) expect(result).toMatchObject({ prefix: '', important: '#app', separator: ':', theme: { screens: { mobile: '400px', }, }, variants: { appearance: ['responsive'], borderCollapse: [], borderColors: ['responsive', 'hover', 'focus'], }, }) }) test('separator key overrides default separator', () => { const userConfig = { separator: '__', } const defaultConfig = { prefix: '', important: false, separator: ':', theme: { screens: { mobile: '400px', }, }, variants: { appearance: ['responsive'], borderCollapse: [], borderColors: ['responsive', 'hover', 'focus'], }, } const result = resolveConfig([userConfig, defaultConfig]) expect(result).toMatchObject({ prefix: '', important: false, separator: '__', theme: { screens: { mobile: '400px', }, }, variants: { appearance: ['responsive'], borderCollapse: [], borderColors: ['responsive', 'hover', 'focus'], }, }) }) test('theme key is merged instead of replaced', () => { const userConfig = { theme: { screens: { mobile: '400px', }, }, } const defaultConfig = { prefix: '-', important: false, separator: ':', theme: { colors: { 'grey-darker': '#606f7b', 'grey-dark': '#8795a1', grey: '#b8c2cc', 'grey-light': '#dae1e7', 'grey-lighter': '#f1f5f8', }, fonts: { sans: ['system-ui', 'BlinkMacSystemFont', '-apple-system', 'Roboto', 'sans-serif'], serif: ['Constantia', 'Lucida Bright', 'Georgia', 'serif'], }, screens: { sm: '500px', md: '750px', lg: '1000px', }, }, variants: { appearance: ['responsive'], borderCollapse: [], borderColors: ['responsive', 'hover', 'focus'], }, } const result = resolveConfig([userConfig, defaultConfig]) expect(result).toMatchObject({ prefix: '-', important: false, separator: ':', theme: { colors: { 'grey-darker': '#606f7b', 'grey-dark': '#8795a1', grey: '#b8c2cc', 'grey-light': '#dae1e7', 'grey-lighter': '#f1f5f8', }, fonts: { sans: ['system-ui', 'BlinkMacSystemFont', '-apple-system', 'Roboto', 'sans-serif'], serif: ['Constantia', 'Lucida Bright', 'Georgia', 'serif'], }, screens: { mobile: '400px', }, }, variants: { appearance: ['responsive'], borderCollapse: [], borderColors: ['responsive', 'hover', 'focus'], }, }) }) test('theme key is deeply merged instead of replaced', () => { const userConfig = { theme: { extend: { colors: { grey: { darker: '#606f7b', dark: '#8795a1', }, }, }, }, } const defaultConfig = { prefix: '-', important: false, separator: ':', theme: { colors: { grey: { grey: '#b8c2cc', light: '#dae1e7', lighter: '#f1f5f8', }, }, }, } const result = resolveConfig([userConfig, defaultConfig]) expect(result).toMatchObject({ prefix: '-', important: false, separator: ':', theme: { colors: { grey: { darker: '#606f7b', dark: '#8795a1', grey: '#b8c2cc', light: '#dae1e7', lighter: '#f1f5f8', }, }, }, }) }) test('variants key is merged instead of replaced', () => { const userConfig = { variants: { backgroundAttachment: [], borderColors: ['responsive', 'hover', 'focus', 'active'], }, } const defaultConfig = { prefix: '-', important: false, separator: ':', theme: { colors: { 'grey-darker': '#606f7b', 'grey-dark': '#8795a1', grey: '#b8c2cc', 'grey-light': '#dae1e7', 'grey-lighter': '#f1f5f8', }, fonts: { sans: ['system-ui', 'BlinkMacSystemFont', '-apple-system', 'Roboto', 'sans-serif'], serif: ['Constantia', 'Lucida Bright', 'Georgia', 'serif'], }, screens: { sm: '500px', md: '750px', lg: '1000px', }, }, variants: { appearance: ['responsive'], backgroundAttachment: ['responsive'], borderCollapse: [], borderColors: ['responsive', 'hover', 'focus'], borderRadius: ['responsive'], }, } const result = resolveConfig([userConfig, defaultConfig]) expect(result).toMatchObject({ prefix: '-', important: false, separator: ':', theme: { colors: { 'grey-darker': '#606f7b', 'grey-dark': '#8795a1', grey: '#b8c2cc', 'grey-light': '#dae1e7', 'grey-lighter': '#f1f5f8', }, fonts: { sans: ['system-ui', 'BlinkMacSystemFont', '-apple-system', 'Roboto', 'sans-serif'], serif: ['Constantia', 'Lucida Bright', 'Georgia', 'serif'], }, screens: { sm: '500px', md: '750px', lg: '1000px', }, }, variants: { appearance: ['responsive'], backgroundAttachment: [], borderCollapse: [], borderColors: ['responsive', 'hover', 'focus', 'active'], borderRadius: ['responsive'], }, }) }) test('a global variants list replaces the default', () => { const userConfig = { variants: ['responsive', 'hover', 'focus', 'active'], } const defaultConfig = { prefix: '-', important: false, separator: ':', theme: { colors: { 'grey-darker': '#606f7b', 'grey-dark': '#8795a1', grey: '#b8c2cc', 'grey-light': '#dae1e7', 'grey-lighter': '#f1f5f8', }, fonts: { sans: ['system-ui', 'BlinkMacSystemFont', '-apple-system', 'Roboto', 'sans-serif'], serif: ['Constantia', 'Lucida Bright', 'Georgia', 'serif'], }, screens: { sm: '500px', md: '750px', lg: '1000px', }, }, variants: { appearance: ['responsive'], backgroundAttachment: ['responsive'], borderCollapse: [], borderColors: ['responsive', 'hover', 'focus'], borderRadius: ['responsive'], }, } const result = resolveConfig([userConfig, defaultConfig]) expect(result).toMatchObject({ prefix: '-', important: false, separator: ':', theme: { colors: { 'grey-darker': '#606f7b', 'grey-dark': '#8795a1', grey: '#b8c2cc', 'grey-light': '#dae1e7', 'grey-lighter': '#f1f5f8', }, fonts: { sans: ['system-ui', 'BlinkMacSystemFont', '-apple-system', 'Roboto', 'sans-serif'], serif: ['Constantia', 'Lucida Bright', 'Georgia', 'serif'], }, screens: { sm: '500px', md: '750px', lg: '1000px', }, }, variants: ['responsive', 'hover', 'focus', 'active'], }) }) test('missing top level keys are pulled from the default config', () => { const userConfig = {} const defaultConfig = { prefix: '-', important: false, separator: ':', theme: { colors: { green: '#00ff00' }, screens: { mobile: '400px', }, }, variants: { appearance: ['responsive'], borderCollapse: [], borderColors: ['responsive', 'hover', 'focus'], }, } const result = resolveConfig([userConfig, defaultConfig]) expect(result).toMatchObject({ prefix: '-', important: false, separator: ':', theme: { colors: { green: '#00ff00' }, screens: { mobile: '400px', }, }, variants: { appearance: ['responsive'], borderCollapse: [], borderColors: ['responsive', 'hover', 'focus'], }, }) }) test('functions in the default theme section are lazily evaluated', () => { const userConfig = { theme: { colors: { red: 'red', green: 'green', blue: 'blue', }, }, } const defaultConfig = { prefix: '-', important: false, separator: ':', theme: { colors: { cyan: 'cyan', magenta: 'magenta', yellow: 'yellow', }, backgroundColors: (theme) => theme('colors'), textColors: (theme) => theme('colors'), }, variants: { backgroundColors: ['responsive', 'hover', 'focus'], textColors: ['responsive', 'hover', 'focus'], }, } const result = resolveConfig([userConfig, defaultConfig]) expect(result).toMatchObject({ prefix: '-', important: false, separator: ':', theme: { colors: { red: 'red', green: 'green', blue: 'blue', }, backgroundColors: { red: 'red', green: 'green', blue: 'blue', }, textColors: { red: 'red', green: 'green', blue: 'blue', }, }, variants: { backgroundColors: ['responsive', 'hover', 'focus'], textColors: ['responsive', 'hover', 'focus'], }, }) }) test('functions in the user theme section are lazily evaluated', () => { const userConfig = { theme: { colors: { red: 'red', green: 'green', blue: 'blue', }, backgroundColors: (theme) => ({ ...theme('colors'), customBackground: '#bada55', }), textColors: (theme) => ({ ...theme('colors'), customText: '#facade', }), }, } const defaultConfig = { prefix: '-', important: false, separator: ':', theme: { colors: { cyan: 'cyan', magenta: 'magenta', yellow: 'yellow', }, backgroundColors: ({ colors }) => colors, textColors: ({ colors }) => colors, }, variants: { backgroundColors: ['responsive', 'hover', 'focus'], textColors: ['responsive', 'hover', 'focus'], }, } const result = resolveConfig([userConfig, defaultConfig]) expect(result).toMatchObject({ prefix: '-', important: false, separator: ':', theme: { colors: { red: 'red', green: 'green', blue: 'blue', }, backgroundColors: { red: 'red', green: 'green', blue: 'blue', customBackground: '#bada55', }, textColors: { red: 'red', green: 'green', blue: 'blue', customText: '#facade', }, }, variants: { backgroundColors: ['responsive', 'hover', 'focus'], textColors: ['responsive', 'hover', 'focus'], }, }) }) 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: (theme) => theme('colors'), }, variants: { backgroundColors: ['responsive', 'hover', 'focus'], opacity: ['responsive', 'hover', 'focus'], }, } const result = resolveConfig([userConfig, defaultConfig]) expect(result).toMatchObject({ 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).toMatchObject({ 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: (theme) => theme('colors'), }, variants: { backgroundColors: ['responsive', 'hover', 'focus'], }, } const result = resolveConfig([userConfig, defaultConfig]) expect(result).toMatchObject({ 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 when they are simple arrays', () => { 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).toMatchObject({ prefix: '-', important: false, separator: ':', theme: { fonts: { sans: ['Comic Sans'], serif: ['Constantia', 'Georgia', 'serif'], mono: ['Menlo', 'Courier New', 'monospace'], }, }, variants: { fonts: ['responsive'], }, }) }) test('theme values in the extend section are deeply merged, when they are arrays of objects', () => { const userConfig = { theme: { extend: { typography: { ArrayArray: { css: [{ a: { backgroundColor: 'red' } }, { a: { color: 'green' } }], }, ObjectArray: { css: { a: { backgroundColor: 'red' } }, }, ArrayObject: { css: [{ a: { backgroundColor: 'red' } }, { a: { color: 'green' } }], }, }, }, }, } const defaultConfig = { prefix: '-', important: false, separator: ':', theme: { typography: { ArrayArray: { css: [{ a: { underline: 'none' } }], }, ObjectArray: { css: [{ a: { underline: 'none' } }], }, ArrayObject: { css: { a: { underline: 'none' } }, }, }, }, variants: {}, } const result = resolveConfig([userConfig, defaultConfig]) expect(result).toMatchObject({ prefix: '-', important: false, separator: ':', theme: { typography: { ArrayArray: { css: [ { a: { underline: 'none' } }, { a: { backgroundColor: 'red' } }, { a: { color: 'green' } }, ], }, ObjectArray: { css: [{ a: { underline: 'none' } }, { a: { backgroundColor: 'red' } }], }, ArrayObject: { css: [ { a: { underline: 'none' } }, { a: { backgroundColor: 'red' } }, { a: { color: 'green' } }, ], }, }, }, variants: {}, }) }) test('the theme function can use a default value if the key is missing', () => { const userConfig = { theme: { colors: { red: 'red', green: 'green', blue: 'blue', }, }, } const defaultConfig = { prefix: '-', important: false, separator: ':', theme: { colors: { cyan: 'cyan', magenta: 'magenta', yellow: 'yellow', }, borderColor: (theme) => ({ default: theme('colors.gray', 'currentColor'), ...theme('colors'), }), }, variants: { borderColor: ['responsive', 'hover', 'focus'], }, } const result = resolveConfig([userConfig, defaultConfig]) expect(result).toMatchObject({ prefix: '-', important: false, separator: ':', theme: { colors: { red: 'red', green: 'green', blue: 'blue', }, borderColor: { default: 'currentColor', red: 'red', green: 'green', blue: 'blue', }, }, variants: { borderColor: ['responsive', 'hover', 'focus'], }, }) }) test('the theme function can resolve function values', () => { const userConfig = { theme: { textColor: (theme) => ({ lime: 'lime', ...theme('colors'), }), backgroundColor: (theme) => ({ orange: 'orange', ...theme('textColor'), }), borderColor: (theme) => theme('backgroundColor'), }, } const defaultConfig = { prefix: '-', important: false, separator: ':', theme: { colors: { red: 'red', green: 'green', blue: 'blue', }, }, variants: { backgroundColor: ['responsive', 'hover', 'focus'], borderColor: ['responsive', 'hover', 'focus'], }, } const result = resolveConfig([userConfig, defaultConfig]) expect(result).toMatchObject({ prefix: '-', important: false, separator: ':', theme: { colors: { red: 'red', green: 'green', blue: 'blue', }, textColor: { lime: 'lime', red: 'red', green: 'green', blue: 'blue', }, backgroundColor: { lime: 'lime', orange: 'orange', red: 'red', green: 'green', blue: 'blue', }, borderColor: { lime: 'lime', orange: 'orange', red: 'red', green: 'green', blue: 'blue', }, }, variants: { backgroundColor: ['responsive', 'hover', 'focus'], borderColor: ['responsive', 'hover', 'focus'], }, }) }) test('the theme function can resolve deep function values', () => { const userConfig = { theme: { minWidth: (theme) => ({ '1/3': theme('width.1/3'), }), }, } const defaultConfig = { prefix: '-', important: false, separator: ':', theme: { spacing: { 0: '0', }, width: (theme) => ({ ...theme('spacing'), '1/3': '33.33333%', }), }, variants: { backgroundColor: ['responsive', 'hover', 'focus'], borderColor: ['responsive', 'hover', 'focus'], }, } const result = resolveConfig([userConfig, defaultConfig]) expect(result).toMatchObject({ prefix: '-', important: false, separator: ':', theme: { spacing: { 0: '0', }, width: { 0: '0', '1/3': '33.33333%', }, minWidth: { '1/3': '33.33333%', }, }, variants: { backgroundColor: ['responsive', 'hover', 'focus'], borderColor: ['responsive', 'hover', 'focus'], }, }) }) test('theme values in the extend section are lazily evaluated', () => { const userConfig = { theme: { colors: { red: 'red', green: 'green', blue: 'blue', }, extend: { colors: { orange: 'orange', }, borderColor: (theme) => ({ foo: theme('colors.orange'), bar: theme('colors.red'), }), }, }, } const defaultConfig = { prefix: '-', important: false, separator: ':', theme: { colors: { cyan: 'cyan', magenta: 'magenta', yellow: 'yellow', }, borderColor: (theme) => ({ default: theme('colors.yellow', 'currentColor'), ...theme('colors'), }), }, variants: { borderColor: ['responsive', 'hover', 'focus'], }, } const result = resolveConfig([userConfig, defaultConfig]) expect(result).toMatchObject({ prefix: '-', important: false, separator: ':', theme: { colors: { orange: 'orange', red: 'red', green: 'green', blue: 'blue', }, borderColor: { default: 'currentColor', foo: 'orange', bar: 'red', orange: 'orange', red: 'red', green: 'green', blue: 'blue', }, }, variants: { borderColor: ['responsive', 'hover', 'focus'], }, }) }) test('lazily evaluated values have access to the config utils', () => { const userConfig = { theme: { inset: (theme) => theme('margin'), shift: (theme, { negative }) => ({ ...theme('spacing'), ...negative(theme('spacing')), }), extend: { nudge: (theme, { negative }) => ({ ...theme('spacing'), ...negative(theme('spacing')), }), }, }, } const defaultConfig = { prefix: '-', important: false, separator: ':', theme: { spacing: { 1: '1px', 2: '2px', 3: '3px', 4: '4px', }, margin: (theme, { negative }) => ({ ...theme('spacing'), ...negative(theme('spacing')), }), }, variants: {}, } const result = resolveConfig([userConfig, defaultConfig]) expect(result).toMatchObject({ prefix: '-', important: false, separator: ':', theme: { spacing: { 1: '1px', 2: '2px', 3: '3px', 4: '4px', }, inset: { '-1': '-1px', '-2': '-2px', '-3': '-3px', '-4': '-4px', 1: '1px', 2: '2px', 3: '3px', 4: '4px', }, margin: { '-1': '-1px', '-2': '-2px', '-3': '-3px', '-4': '-4px', 1: '1px', 2: '2px', 3: '3px', 4: '4px', }, shift: { '-1': '-1px', '-2': '-2px', '-3': '-3px', '-4': '-4px', 1: '1px', 2: '2px', 3: '3px', 4: '4px', }, nudge: { '-1': '-1px', '-2': '-2px', '-3': '-3px', '-4': '-4px', 1: '1px', 2: '2px', 3: '3px', 4: '4px', }, }, variants: {}, }) }) test('the original theme is not mutated', () => { const userConfig = { theme: { extend: { colors: { orange: 'orange', }, }, }, variants: { borderColor: ['responsive', 'hover'], }, } const defaultConfig = { prefix: '-', important: false, separator: ':', theme: { colors: { cyan: 'cyan', magenta: 'magenta', yellow: 'yellow', }, }, variants: { backgroundColor: ['responsive', 'hover', 'focus'], }, } resolveConfig([userConfig, defaultConfig]) expect(userConfig).toEqual({ theme: { extend: { colors: { orange: 'orange', }, }, }, variants: { borderColor: ['responsive', 'hover'], }, }) }) test('custom properties are multiplied by -1 for negative values', () => { const userConfig = { theme: { spacing: { 1: '1px', 2: '2px', 3: '3px', 4: '4px', foo: 'var(--foo)', bar: 'var(--bar, 500px)', baz: 'calc(50% - 10px)', }, margin: (theme, { negative }) => ({ ...theme('spacing'), ...negative(theme('spacing')), }), }, } const defaultConfig = { prefix: '-', important: false, separator: ':', theme: {}, variants: {}, } const result = resolveConfig([userConfig, defaultConfig]) expect(result).toMatchObject({ prefix: '-', important: false, separator: ':', theme: { spacing: { 1: '1px', 2: '2px', 3: '3px', 4: '4px', foo: 'var(--foo)', bar: 'var(--bar, 500px)', baz: 'calc(50% - 10px)', }, margin: { 1: '1px', 2: '2px', 3: '3px', 4: '4px', foo: 'var(--foo)', bar: 'var(--bar, 500px)', baz: 'calc(50% - 10px)', '-1': '-1px', '-2': '-2px', '-3': '-3px', '-4': '-4px', '-foo': 'calc(var(--foo) * -1)', '-bar': 'calc(var(--bar, 500px) * -1)', '-baz': 'calc(-50% - -10px)', }, }, variants: {}, }) }) test('more than two config objects can be resolved', () => { const firstConfig = { theme: { extend: { fontFamily: () => ({ code: ['Menlo', 'monospace'], }), colors: { red: 'red', }, backgroundColor: { customBackgroundOne: '#bada55', }, textDecorationColor: { orange: 'orange', }, }, }, } const secondConfig = { prefix: '-', important: false, separator: ':', theme: { extend: { fontFamily: { quote: ['Helvetica', 'serif'], }, colors: { green: 'green', }, backgroundColor: { customBackgroundTwo: '#facade', }, textDecorationColor: (theme) => theme('colors'), }, }, } const thirdConfig = { prefix: '-', important: false, separator: ':', theme: { extend: { fontFamily: { hero: ['Futura', 'sans-serif'], }, colors: { pink: 'pink', }, backgroundColor: () => ({ customBackgroundThree: '#c0ffee', }), textDecorationColor: { lime: 'lime', }, }, }, } const defaultConfig = { prefix: '-', important: false, separator: ':', theme: { fontFamily: { body: ['Arial', 'sans-serif'], display: ['Georgia', 'serif'], }, colors: { blue: 'blue', }, backgroundColor: (theme) => theme('colors'), }, variants: { backgroundColor: ['responsive', 'hover', 'focus'], }, } const result = resolveConfig([firstConfig, secondConfig, thirdConfig, defaultConfig]) expect(result).toMatchObject({ prefix: '-', important: false, separator: ':', theme: { fontFamily: { body: ['Arial', 'sans-serif'], display: ['Georgia', 'serif'], code: ['Menlo', 'monospace'], quote: ['Helvetica', 'serif'], hero: ['Futura', 'sans-serif'], }, colors: { red: 'red', green: 'green', blue: 'blue', pink: 'pink', }, backgroundColor: { red: 'red', green: 'green', blue: 'blue', pink: 'pink', customBackgroundOne: '#bada55', customBackgroundTwo: '#facade', customBackgroundThree: '#c0ffee', }, textDecorationColor: { red: 'red', green: 'green', blue: 'blue', pink: 'pink', orange: 'orange', lime: 'lime', }, }, variants: { backgroundColor: ['responsive', 'hover', 'focus'], }, }) }) test('plugin config modifications are applied', () => { const userConfig = { plugins: [ { config: { prefix: 'tw-', }, }, ], } const defaultConfig = { prefix: '', important: false, separator: ':', theme: { screens: { mobile: '400px', }, }, variants: { appearance: ['responsive'], borderCollapse: [], borderColors: ['responsive', 'hover', 'focus'], }, } const result = resolveConfig([userConfig, defaultConfig]) expect(result).toMatchObject({ prefix: 'tw-', important: false, separator: ':', theme: { screens: { mobile: '400px', }, }, variants: { appearance: ['responsive'], borderCollapse: [], borderColors: ['responsive', 'hover', 'focus'], }, plugins: userConfig.plugins, }) }) test('user config takes precedence over plugin config modifications', () => { const userConfig = { prefix: 'user-', plugins: [ { config: { prefix: 'tw-', }, }, ], } const defaultConfig = { prefix: '', important: false, separator: ':', theme: { screens: { mobile: '400px', }, }, variants: { appearance: ['responsive'], borderCollapse: [], borderColors: ['responsive', 'hover', 'focus'], }, } const result = resolveConfig([userConfig, defaultConfig]) expect(result).toMatchObject({ prefix: 'user-', important: false, separator: ':', theme: { screens: { mobile: '400px', }, }, variants: { appearance: ['responsive'], borderCollapse: [], borderColors: ['responsive', 'hover', 'focus'], }, plugins: userConfig.plugins, }) }) test('plugin config can register plugins that also have config', () => { const userConfig = { plugins: [ { config: { prefix: 'tw-', plugins: [ { config: { important: true, }, }, { config: { separator: '__', }, }, ], }, handler() {}, }, ], } const defaultConfig = { prefix: '', important: false, separator: ':', theme: { screens: { mobile: '400px', }, }, variants: { appearance: ['responsive'], borderCollapse: [], borderColors: ['responsive', 'hover', 'focus'], }, } const result = resolveConfig([userConfig, defaultConfig]) expect(result).toMatchObject({ prefix: 'tw-', important: true, separator: '__', theme: { screens: { mobile: '400px', }, }, variants: { appearance: ['responsive'], borderCollapse: [], borderColors: ['responsive', 'hover', 'focus'], }, plugins: userConfig.plugins, }) }) test('plugin configs take precedence over plugin configs registered by that plugin', () => { const userConfig = { plugins: [ { config: { prefix: 'outer-', plugins: [ { config: { prefix: 'inner-', }, }, ], }, handler() {}, }, ], } const defaultConfig = { prefix: '', important: false, separator: ':', theme: { screens: { mobile: '400px', }, }, variants: { appearance: ['responsive'], borderCollapse: [], borderColors: ['responsive', 'hover', 'focus'], }, } const result = resolveConfig([userConfig, defaultConfig]) expect(result).toMatchObject({ prefix: 'outer-', important: false, separator: ':', theme: { screens: { mobile: '400px', }, }, variants: { appearance: ['responsive'], borderCollapse: [], borderColors: ['responsive', 'hover', 'focus'], }, plugins: userConfig.plugins, }) }) test('plugin theme extensions are added even if user overrides top-level theme config', () => { const userConfig = { theme: { width: { '1px': '1px', }, }, plugins: [ { config: { theme: { extend: { width: { '2px': '2px', '3px': '3px', }, }, }, }, handler() {}, }, ], } const defaultConfig = { prefix: '', important: false, separator: ':', theme: { width: { sm: '1rem', md: '2rem', lg: '3rem', }, screens: { mobile: '400px', }, }, variants: { appearance: ['responsive'], borderCollapse: [], borderColors: ['responsive', 'hover', 'focus'], }, } const result = resolveConfig([userConfig, defaultConfig]) expect(result).toMatchObject({ prefix: '', important: false, separator: ':', theme: { width: { '1px': '1px', '2px': '2px', '3px': '3px', }, screens: { mobile: '400px', }, }, variants: { appearance: ['responsive'], borderCollapse: [], borderColors: ['responsive', 'hover', 'focus'], }, plugins: userConfig.plugins, }) }) test('user theme extensions take precedence over plugin theme extensions with the same key', () => { const userConfig = { theme: { extend: { width: { xl: '6rem', }, }, }, plugins: [ { config: { theme: { extend: { width: { xl: '4rem', }, }, }, }, handler() {}, }, ], } const defaultConfig = { prefix: '', important: false, separator: ':', theme: { width: { sm: '1rem', md: '2rem', lg: '3rem', }, screens: { mobile: '400px', }, }, variants: { appearance: ['responsive'], borderCollapse: [], borderColors: ['responsive', 'hover', 'focus'], }, } const result = resolveConfig([userConfig, defaultConfig]) expect(result).toMatchObject({ prefix: '', important: false, separator: ':', theme: { width: { sm: '1rem', md: '2rem', lg: '3rem', xl: '6rem', }, screens: { mobile: '400px', }, }, variants: { appearance: ['responsive'], borderCollapse: [], borderColors: ['responsive', 'hover', 'focus'], }, plugins: userConfig.plugins, }) }) test('variants can be extended', () => { const userConfig = { variants: { borderColor: ({ after }) => after(['group-focus'], 'hover'), extend: { backgroundColor: ['active', 'disabled', 'group-hover'], }, }, } const otherConfig = { variants: { extend: { textColor: ['hover', 'focus-within'], }, }, } const defaultConfig = { prefix: '', important: false, separator: ':', theme: {}, variants: { borderColor: ['hover', 'focus'], backgroundColor: ['responsive', 'hover', 'focus'], textColor: ['responsive', 'focus'], }, } const result = resolveConfig([userConfig, otherConfig, defaultConfig]) expect(result).toMatchObject({ variants: { borderColor: ['hover', 'group-focus', 'focus'], backgroundColor: ['responsive', 'group-hover', 'hover', 'focus', 'active', 'disabled'], textColor: ['responsive', 'focus-within', 'hover', 'focus'], }, }) }) test('extensions are applied in the right order', () => { const userConfig = { theme: { extend: { colors: { grey: { light: '#eee', }, }, }, }, } const otherConfig = { theme: { extend: { colors: { grey: { light: '#ddd', darker: '#111', }, }, }, }, } const anotherConfig = { theme: { extend: { colors: { grey: { darker: '#222', }, }, }, }, } const defaultConfig = { theme: { colors: { grey: { light: '#ccc', dark: '#333', }, }, }, } const result = resolveConfig([userConfig, otherConfig, anotherConfig, defaultConfig]) expect(result).toMatchObject({ theme: { colors: { grey: { light: '#eee', dark: '#333', darker: '#111', }, }, }, }) }) test('variant sort order can be customized', () => { const userConfig = { variantOrder: [ 'disabled', 'focus', 'group-hover', 'focus-within', 'active', 'hover', 'responsive', ], variants: { borderColor: ({ after }) => after(['group-focus'], 'hover'), extend: { backgroundColor: ['active', 'disabled', 'group-hover'], }, }, } const otherConfig = { variants: { extend: { textColor: ['hover', 'focus-within'], }, }, } const defaultConfig = { prefix: '', important: false, separator: ':', theme: {}, variants: { borderColor: ['hover', 'focus'], backgroundColor: ['responsive', 'hover', 'focus'], textColor: ['responsive', 'focus'], }, } const result = resolveConfig([userConfig, otherConfig, defaultConfig]) expect(result).toMatchObject({ variants: { borderColor: ['hover', 'group-focus', 'focus'], backgroundColor: ['disabled', 'focus', 'group-hover', 'active', 'hover', 'responsive'], textColor: ['focus', 'focus-within', 'hover', 'responsive'], }, }) }) test('custom variants go to the beginning by default when sort is applied', () => { const userConfig = { variants: { extend: { backgroundColor: ['active', 'custom-variant-1', 'group-hover', 'custom-variant-2'], }, }, } const defaultConfig = { prefix: '', important: false, separator: ':', theme: {}, variants: { backgroundColor: ['responsive', 'hover', 'focus'], }, } const result = resolveConfig([userConfig, defaultConfig]) expect(result).toMatchObject({ variants: { backgroundColor: [ 'responsive', 'custom-variant-1', 'custom-variant-2', 'group-hover', 'hover', 'focus', 'active', ], }, }) }) test('variants can be defined as a function', () => { const userConfig = { variants: { backgroundColor: ({ variants }) => [...variants('backgroundColor'), 'disabled'], padding: ({ before }) => before(['active']), float: ({ before }) => before(['disabled'], 'focus'), margin: ({ before }) => before(['hover'], 'focus'), borderWidth: ({ after }) => after(['active']), backgroundImage: ({ after }) => after(['disabled'], 'hover'), opacity: ({ after }) => after(['hover'], 'focus'), rotate: ({ without }) => without(['hover']), cursor: ({ before, after, without }) => without(['responsive'], before(['checked'], 'hover', after(['hover'], 'focus'))), }, } const otherConfig = { variants: { backgroundColor: ({ variants }) => [...variants('backgroundColor'), 'active'], }, } const defaultConfig = { prefix: '', important: false, separator: ':', theme: {}, variants: { backgroundColor: ['responsive', 'hover', 'focus'], padding: ['responsive', 'focus'], float: ['responsive', 'hover', 'focus'], margin: ['responsive'], borderWidth: ['responsive', 'focus'], backgroundImage: ['responsive', 'hover', 'focus'], opacity: ['responsive'], rotate: ['responsive', 'hover', 'focus'], cursor: ['responsive', 'focus'], }, } const result = resolveConfig([userConfig, otherConfig, defaultConfig]) expect(result).toMatchObject({ prefix: '', important: false, separator: ':', theme: {}, variants: { backgroundColor: ['responsive', 'hover', 'focus', 'active', 'disabled'], padding: ['active', 'responsive', 'focus'], float: ['responsive', 'hover', 'disabled', 'focus'], margin: ['responsive', 'hover'], borderWidth: ['responsive', 'focus', 'active'], backgroundImage: ['responsive', 'hover', 'disabled', 'focus'], opacity: ['hover', 'responsive'], rotate: ['responsive', 'focus'], cursor: ['focus', 'checked', 'hover'], }, }) }) test('core plugin configuration builds on the default list when starting with an empty object', () => { const userConfig = { corePlugins: { display: false }, } const defaultConfig = { prefix: '', important: false, separator: ':', theme: {}, variants: {}, corePlugins: {}, } const result = resolveConfig([userConfig, defaultConfig]) expect(result).toMatchObject({ prefix: '', important: false, separator: ':', theme: {}, variants: {}, corePlugins: corePluginList.filter((c) => c !== 'display'), }) }) test('core plugin configurations stack', () => { const userConfig = { corePlugins: { display: false }, } const otherConfig = { corePlugins: ({ corePlugins }) => { return [...corePlugins, 'margin'] }, } const defaultConfig = { prefix: '', important: false, separator: ':', theme: {}, variants: {}, corePlugins: ['float', 'display', 'padding'], } const result = resolveConfig([userConfig, otherConfig, defaultConfig]) expect(result).toMatchObject({ prefix: '', important: false, separator: ':', theme: {}, variants: {}, corePlugins: ['float', 'padding', 'margin'], }) }) test('plugins are merged', () => { const userConfig = { plugins: ['3'], } const otherConfig = { plugins: ['2'], } const defaultConfig = { plugins: ['1'], prefix: '', important: false, separator: ':', theme: {}, variants: {}, } const result = resolveConfig([userConfig, otherConfig, defaultConfig]) expect(result).toMatchObject({ prefix: '', important: false, separator: ':', theme: {}, variants: {}, plugins: ['1', '2', '3'], }) })