import createPlugin from '../src/public/create-plugin'
import { run, html, css, defaults } from './util/run'

test('plugins can create utilities with object syntax', () => {
  let config = {
    content: [
      {
        raw: html`<div class="custom-object-fill custom-object-contain custom-object-cover"></div>`,
      },
    ],
    plugins: [
      function ({ addUtilities }) {
        addUtilities({
          '.custom-object-fill': {
            'object-fit': 'fill',
          },
          '.custom-object-contain': {
            'object-fit': 'contain',
          },
          '.custom-object-cover': {
            'object-fit': 'cover',
          },
        })
      },
    ],
  }

  return run('@tailwind utilities', config).then((result) => {
    expect(result.css).toMatchFormattedCss(css`
      .custom-object-fill {
        object-fit: fill;
      }
      .custom-object-contain {
        object-fit: contain;
      }
      .custom-object-cover {
        object-fit: cover;
      }
    `)
  })
})

test('plugins can create utilities with arrays of objects', () => {
  let config = {
    content: [
      {
        raw: html`<div class="custom-object-fill custom-object-contain custom-object-cover"></div>`,
      },
    ],
    plugins: [
      function ({ addUtilities }) {
        addUtilities([
          {
            '.custom-object-fill': {
              'object-fit': 'fill',
            },
            '.custom-object-contain': {
              'object-fit': 'contain',
            },
            '.custom-object-cover': {
              'object-fit': 'cover',
            },
          },
        ])
      },
    ],
  }

  return run('@tailwind utilities', config).then((result) => {
    expect(result.css).toMatchFormattedCss(css`
      .custom-object-fill {
        object-fit: fill;
      }
      .custom-object-contain {
        object-fit: contain;
      }
      .custom-object-cover {
        object-fit: cover;
      }
    `)
  })
})

test('plugins can create utilities with raw PostCSS nodes', () => {
  let config = {
    content: [
      {
        raw: html`<div class="custom-object-fill custom-object-contain custom-object-cover"></div>`,
      },
    ],
    plugins: [
      function ({ addUtilities, postcss }) {
        addUtilities([
          postcss.rule({ selector: '.custom-object-fill' }).append([
            postcss.decl({
              prop: 'object-fit',
              value: 'fill',
            }),
          ]),
          postcss.rule({ selector: '.custom-object-contain' }).append([
            postcss.decl({
              prop: 'object-fit',
              value: 'contain',
            }),
          ]),
          postcss.rule({ selector: '.custom-object-cover' }).append([
            postcss.decl({
              prop: 'object-fit',
              value: 'cover',
            }),
          ]),
        ])
      },
    ],
  }

  return run('@tailwind utilities', config).then((result) => {
    expect(result.css).toMatchFormattedCss(css`
      .custom-object-fill {
        object-fit: fill;
      }
      .custom-object-contain {
        object-fit: contain;
      }
      .custom-object-cover {
        object-fit: cover;
      }
    `)
  })
})

test('plugins can create utilities with mixed object styles and PostCSS nodes', () => {
  let config = {
    content: [
      {
        raw: html`<div class="custom-object-fill custom-object-contain custom-object-cover"></div>`,
      },
    ],
    plugins: [
      function ({ addUtilities, postcss }) {
        addUtilities([
          {
            '.custom-object-fill': {
              objectFit: 'fill',
            },
          },
          postcss.rule({ selector: '.custom-object-contain' }).append([
            postcss.decl({
              prop: 'object-fit',
              value: 'contain',
            }),
          ]),
          postcss.rule({ selector: '.custom-object-cover' }).append([
            postcss.decl({
              prop: 'object-fit',
              value: 'cover',
            }),
          ]),
        ])
      },
    ],
  }

  return run('@tailwind utilities', config).then((result) => {
    expect(result.css).toMatchFormattedCss(css`
      .custom-object-fill {
        object-fit: fill;
      }
      .custom-object-contain {
        object-fit: contain;
      }
      .custom-object-cover {
        object-fit: cover;
      }
    `)
  })
})

test('plugins can create components with object syntax', () => {
  let config = {
    content: [
      {
        raw: html`<button class="btn-blue"></button>`,
      },
    ],
    plugins: [
      function ({ addComponents }) {
        addComponents({
          '.btn-blue': {
            backgroundColor: 'blue',
            color: 'white',
            padding: '.5rem 1rem',
            borderRadius: '.25rem',
          },
          '.btn-blue:hover': {
            backgroundColor: 'darkblue',
          },
        })
      },
    ],
  }

  return run('@tailwind components', config).then((result) => {
    expect(result.css).toMatchFormattedCss(css`
      .btn-blue {
        background-color: blue;
        color: white;
        padding: 0.5rem 1rem;
        border-radius: 0.25rem;
      }
      .btn-blue:hover {
        background-color: darkblue;
      }
    `)
  })
})

test('plugins can add base styles with object syntax', () => {
  let config = {
    content: [
      {
        raw: html`<img /><button></button>`,
      },
    ],
    plugins: [
      function ({ addBase }) {
        addBase({
          img: {
            maxWidth: '100%',
          },
          button: {
            fontFamily: 'inherit',
          },
        })
      },
    ],
    corePlugins: { preflight: false },
  }

  return run('@tailwind base', config).then((result) => {
    expect(result.css).toMatchFormattedCss(css`
      img {
        max-width: 100%;
      }
      button {
        font-family: inherit;
      }

      ${defaults}
    `)
  })
})

test('plugins can add base styles with raw PostCSS nodes', () => {
  let config = {
    content: [
      {
        raw: html`<img /><button></button>`,
      },
    ],
    plugins: [
      function ({ addBase, postcss }) {
        addBase([
          postcss.rule({ selector: 'img' }).append([
            postcss.decl({
              prop: 'max-width',
              value: '100%',
            }),
          ]),
          postcss.rule({ selector: 'button' }).append([
            postcss.decl({
              prop: 'font-family',
              value: 'inherit',
            }),
          ]),
        ])
      },
    ],
    corePlugins: { preflight: false },
  }

  return run('@tailwind base', config).then((result) => {
    expect(result.css).toMatchFormattedCss(css`
      img {
        max-width: 100%;
      }
      button {
        font-family: inherit;
      }
      ${defaults}
    `)
  })
})

test('plugins can create components with raw PostCSS nodes', () => {
  let config = {
    content: [
      {
        raw: html`<button class="btn-blue"></button>`,
      },
    ],
    plugins: [
      function ({ addComponents, postcss }) {
        addComponents([
          postcss.rule({ selector: '.btn-blue' }).append([
            postcss.decl({
              prop: 'background-color',
              value: 'blue',
            }),
            postcss.decl({
              prop: 'color',
              value: 'white',
            }),
            postcss.decl({
              prop: 'padding',
              value: '.5rem 1rem',
            }),
            postcss.decl({
              prop: 'border-radius',
              value: '.25rem',
            }),
          ]),
          postcss.rule({ selector: '.btn-blue:hover' }).append([
            postcss.decl({
              prop: 'background-color',
              value: 'darkblue',
            }),
          ]),
        ])
      },
    ],
  }

  return run('@tailwind components', config).then((result) => {
    expect(result.css).toMatchFormattedCss(css`
      .btn-blue {
        background-color: blue;
        color: white;
        padding: 0.5rem 1rem;
        border-radius: 0.25rem;
      }
      .btn-blue:hover {
        background-color: darkblue;
      }
    `)
  })
})

test('plugins can create components with mixed object styles and raw PostCSS nodes', () => {
  let config = {
    content: [
      {
        raw: html`<button class="btn-blue"></button>`,
      },
    ],
    plugins: [
      function ({ addComponents, postcss }) {
        addComponents([
          postcss.rule({ selector: '.btn-blue' }).append([
            postcss.decl({
              prop: 'background-color',
              value: 'blue',
            }),
            postcss.decl({
              prop: 'color',
              value: 'white',
            }),
            postcss.decl({
              prop: 'padding',
              value: '.5rem 1rem',
            }),
            postcss.decl({
              prop: 'border-radius',
              value: '.25rem',
            }),
          ]),
          {
            '.btn-blue:hover': {
              backgroundColor: 'darkblue',
            },
          },
        ])
      },
    ],
  }

  return run('@tailwind components', config).then((result) => {
    expect(result.css).toMatchFormattedCss(css`
      .btn-blue {
        background-color: blue;
        color: white;
        padding: 0.5rem 1rem;
        border-radius: 0.25rem;
      }
      .btn-blue:hover {
        background-color: darkblue;
      }
    `)
  })
})

test('plugins can create components with media queries with object syntax', () => {
  let config = {
    content: [
      {
        raw: html`<div class="custom-container"></div>`,
      },
    ],
    plugins: [
      function ({ addComponents }) {
        addComponents({
          '.custom-container': {
            width: '100%',
          },
          '@media (min-width: 100px)': {
            '.custom-container': {
              maxWidth: '100px',
            },
          },
          '@media (min-width: 200px)': {
            '.custom-container': {
              maxWidth: '200px',
            },
          },
          '@media (min-width: 300px)': {
            '.custom-container': {
              maxWidth: '300px',
            },
          },
        })
      },
    ],
  }

  return run('@tailwind components', config).then((result) => {
    expect(result.css).toMatchFormattedCss(css`
      .custom-container {
        width: 100%;
      }
      @media (min-width: 100px) {
        .custom-container {
          max-width: 100px;
        }
      }
      @media (min-width: 200px) {
        .custom-container {
          max-width: 200px;
        }
      }
      @media (min-width: 300px) {
        .custom-container {
          max-width: 300px;
        }
      }
    `)
  })
})

test('media queries can be defined multiple times using objects-in-array syntax', () => {
  let config = {
    content: [
      {
        raw: html`<div class="custom-container"></div>
          <button class="btn"></button>`,
      },
    ],
    plugins: [
      function ({ addComponents }) {
        addComponents([
          {
            '.custom-container': {
              width: '100%',
            },
            '@media (min-width: 100px)': {
              '.custom-container': {
                maxWidth: '100px',
              },
            },
          },
          {
            '.btn': {
              padding: '1rem .5rem',
              display: 'block',
            },
            '@media (min-width: 100px)': {
              '.btn': {
                display: 'inline-block',
              },
            },
          },
        ])
      },
    ],
  }

  return run('@tailwind components', config).then((result) => {
    expect(result.css).toMatchFormattedCss(css`
      .custom-container {
        width: 100%;
      }
      @media (min-width: 100px) {
        .custom-container {
          max-width: 100px;
        }
      }
      .btn {
        padding: 1rem 0.5rem;
        display: block;
      }
      @media (min-width: 100px) {
        .btn {
          display: inline-block;
        }
      }
    `)
  })
})

test('plugins can create nested rules', () => {
  let config = {
    content: [{ raw: html`<button class="btn-blue"></button>` }],
    plugins: [
      function ({ addComponents }) {
        addComponents({
          '.btn-blue': {
            backgroundColor: 'blue',
            color: 'white',
            padding: '.5rem 1rem',
            borderRadius: '.25rem',
            '&:hover': {
              backgroundColor: 'darkblue',
            },
            '@media (min-width: 500px)': {
              '&:hover': {
                backgroundColor: 'orange',
              },
            },
            '> a': {
              color: 'red',
            },
            'h1 &': {
              color: 'purple',
            },
          },
        })
      },
    ],
  }

  return run('@tailwind components', config).then((result) => {
    expect(result.css).toMatchFormattedCss(css`
      .btn-blue {
        background-color: blue;
        color: white;
        padding: 0.5rem 1rem;
        border-radius: 0.25rem;
      }
      .btn-blue:hover {
        background-color: darkblue;
      }
      @media (min-width: 500px) {
        .btn-blue:hover {
          background-color: orange;
        }
      }
      .btn-blue > a {
        color: red;
      }
      h1 .btn-blue {
        color: purple;
      }
    `)
  })
})

test('plugins can create rules with escaped selectors', () => {
  let config = {
    content: [{ raw: html`<div class="custom-top-1/4"></div>` }],
    plugins: [
      function ({ e, addUtilities }) {
        addUtilities({
          [`.${e('custom-top-1/4')}`]: {
            top: '25%',
          },
        })
      },
    ],
  }

  return run('@tailwind utilities', config).then((result) => {
    expect(result.css).toMatchFormattedCss(css`
      .custom-top-1\/4 {
        top: 25%;
      }
    `)
  })
})

test('plugins can access the current config', () => {
  let config = {
    content: [{ raw: html`<div class="custom-container"></div>` }],
    plugins: [
      function ({ addComponents, config }) {
        let containerClasses = [
          {
            '.custom-container': {
              width: '100%',
            },
          },
        ]

        for (let maxWidth of Object.values(config('theme.customScreens'))) {
          containerClasses.push({
            [`@media (min-width: ${maxWidth})`]: {
              '.custom-container': { maxWidth },
            },
          })
        }

        addComponents(containerClasses)
      },
    ],
    theme: {
      customScreens: {
        sm: '576px',
        md: '768px',
        lg: '992px',
        xl: '1200px',
      },
    },
  }

  return run('@tailwind components', config).then((result) => {
    expect(result.css).toMatchFormattedCss(css`
      .custom-container {
        width: 100%;
      }
      @media (min-width: 576px) {
        .custom-container {
          max-width: 576px;
        }
      }
      @media (min-width: 768px) {
        .custom-container {
          max-width: 768px;
        }
      }
      @media (min-width: 992px) {
        .custom-container {
          max-width: 992px;
        }
      }
      @media (min-width: 1200px) {
        .custom-container {
          max-width: 1200px;
        }
      }
    `)
  })
})

test('plugins can check if corePlugins are enabled', () => {
  let config = {
    content: [{ raw: html`<div class="test"></div>` }],
    plugins: [
      function ({ addUtilities, corePlugins }) {
        addUtilities({
          '.test': {
            'text-color': corePlugins('textColor') ? 'true' : 'false',
            opacity: corePlugins('opacity') ? 'true' : 'false',
          },
        })
      },
    ],
    corePlugins: { textColor: false },
  }

  return run('@tailwind utilities', config).then((result) => {
    expect(result.css).toMatchFormattedCss(css`
      .test {
        text-color: false;
        opacity: true;
      }
    `)
  })
})

test('plugins can check if corePlugins are enabled when using array white-listing', () => {
  let config = {
    content: [{ raw: html`<div class="test"></div>` }],
    plugins: [
      function ({ addUtilities, corePlugins }) {
        addUtilities({
          '.test': {
            'text-color': corePlugins('textColor') ? 'true' : 'false',
            opacity: corePlugins('opacity') ? 'true' : 'false',
          },
        })
      },
    ],
    corePlugins: ['textColor'],
  }

  return run('@tailwind utilities', config).then((result) => {
    expect(result.css).toMatchFormattedCss(css`
      .test {
        text-color: true;
        opacity: false;
      }
    `)
  })
})

test('plugins can provide fallbacks to keys missing from the config', () => {
  let config = {
    content: [{ raw: html`<button class="btn"></button>` }],
    plugins: [
      function ({ addComponents, config }) {
        addComponents({
          '.btn': {
            borderRadius: config('borderRadius.default', '.25rem'),
          },
        })
      },
    ],
    theme: {
      borderRadius: {
        1: '1px',
        2: '2px',
        4: '4px',
        8: '8px',
      },
    },
  }

  return run('@tailwind components', config).then((result) => {
    expect(result.css).toMatchFormattedCss(css`
      .btn {
        border-radius: 0.25rem;
      }
    `)
  })
})

test('plugins can add multiple sets of utilities and components', () => {
  let config = {
    content: [
      {
        raw: html`<button class="btn"></button>
          <div class="card custom-skew-12deg custom-border-collapse"></div>`,
      },
    ],
    plugins: [
      function ({ addUtilities, addComponents }) {
        addComponents({
          '.card': {
            padding: '1rem',
            borderRadius: '.25rem',
          },
        })

        addUtilities({
          '.custom-skew-12deg': {
            transform: 'skewY(-12deg)',
          },
        })

        addComponents({
          '.btn': {
            padding: '1rem .5rem',
            display: 'inline-block',
          },
        })

        addUtilities({
          '.custom-border-collapse': {
            borderCollapse: 'collapse',
          },
        })
      },
    ],
  }

  return run('@tailwind components; @tailwind utilities;', config).then((result) => {
    expect(result.css).toMatchFormattedCss(css`
      .card {
        padding: 1rem;
        border-radius: 0.25rem;
      }
      .btn {
        padding: 1rem 0.5rem;
        display: inline-block;
      }
      .custom-skew-12deg {
        transform: skewY(-12deg);
      }
      .custom-border-collapse {
        border-collapse: collapse;
      }
    `)
  })
})

test('plugins respect prefix and important options by default when adding utilities', () => {
  let config = {
    prefix: 'tw-',
    important: true,
    content: [{ raw: html`<div class="tw-custom-rotate-90"></div>` }],
    plugins: [
      function ({ addUtilities }) {
        addUtilities({
          '.custom-rotate-90': {
            transform: 'rotate(90deg)',
          },
        })
      },
    ],
  }

  return run('@tailwind utilities', config).then((result) => {
    expect(result.css).toMatchFormattedCss(css`
      .tw-custom-rotate-90 {
        transform: rotate(90deg) !important;
      }
    `)
  })
})

test('when important is a selector it is used to scope utilities instead of adding !important', () => {
  let config = {
    prefix: 'tw-',
    important: '#app',
    content: [{ raw: html`<div class="tw-custom-rotate-90"></div>` }],
    plugins: [
      function ({ addUtilities }) {
        addUtilities({
          '.custom-rotate-90': {
            transform: 'rotate(90deg)',
          },
        })
      },
    ],
  }

  return run('@tailwind utilities', config).then((result) => {
    expect(result.css).toMatchFormattedCss(css`
      #app .tw-custom-rotate-90 {
        transform: rotate(90deg);
      }
    `)
  })
})

test('when important is a selector it scopes all selectors in a rule, even though defining utilities like this is stupid', () => {
  let config = {
    important: '#app',
    content: [{ raw: html`<div class="custom-rotate-90 custom-rotate-1/4"></div>` }],
    plugins: [
      function ({ addUtilities }) {
        addUtilities({
          '.custom-rotate-90, .custom-rotate-1\\/4': {
            transform: 'rotate(90deg)',
          },
        })
      },
    ],
  }

  return run('@tailwind utilities', config).then((result) => {
    expect(result.css).toMatchFormattedCss(css`
      #app .custom-rotate-90,
      #app .custom-rotate-1\/4 {
        transform: rotate(90deg);
      }
    `)
  })
})

test('important utilities are not made double important when important option is used', () => {
  let config = {
    important: true,
    content: [{ raw: html`<div class="custom-rotate-90"></div>` }],
    plugins: [
      function ({ addUtilities }) {
        addUtilities({
          '.custom-rotate-90': {
            transform: 'rotate(90deg) !important',
          },
        })
      },
    ],
  }

  return run('@tailwind utilities', config).then((result) => {
    expect(result.css).toMatchFormattedCss(css`
      .custom-rotate-90 {
        transform: rotate(90deg) !important;
      }
    `)
  })
})

test("component declarations respect the 'prefix' option by default", () => {
  let config = {
    prefix: 'tw-',
    content: [{ raw: html`<button class="tw-btn-blue"></button>` }],
    plugins: [
      function ({ addComponents }) {
        addComponents({
          '.btn-blue': {
            backgroundColor: 'blue',
          },
        })
      },
    ],
  }

  return run('@tailwind components', config).then((result) => {
    expect(result.css).toMatchFormattedCss(css`
      .tw-btn-blue {
        background-color: blue;
      }
    `)
  })
})

test('all selectors in a rule are prefixed', () => {
  let config = {
    prefix: 'tw-',
    content: [
      {
        raw: html`<button class="tw-btn-blue tw-btn-red"></button>
          <div class="tw-custom-rotate-90 tw-custom-rotate-1/4"></div>`,
      },
    ],
    plugins: [
      function ({ addUtilities, addComponents }) {
        addUtilities({
          '.custom-rotate-90, .custom-rotate-1\\/4': {
            transform: 'rotate(90deg)',
          },
        })
        addComponents({
          '.btn-blue, .btn-red': {
            padding: '10px',
          },
        })
      },
    ],
  }

  return run('@tailwind components', config).then((result) => {
    expect(result.css).toMatchFormattedCss(css`
      .tw-btn-blue,
      .tw-btn-red {
        padding: 10px;
      }
    `)
  })
})

test("component declarations can optionally ignore 'prefix' option", () => {
  let config = {
    prefix: 'tw-',
    content: [{ raw: html`<button class="btn-blue"></button>` }],
    plugins: [
      function ({ addComponents }) {
        addComponents(
          {
            '.btn-blue': {
              backgroundColor: 'blue',
            },
          },
          { respectPrefix: false }
        )
      },
    ],
  }

  return run('@tailwind components', config).then((result) => {
    expect(result.css).toMatchFormattedCss(css`
      .btn-blue {
        background-color: blue;
      }
    `)
  })
})

test("component declarations are not affected by the 'important' option", () => {
  let config = {
    important: true,
    content: [{ raw: html`<button class="btn-blue"></button>` }],
    plugins: [
      function ({ addComponents }) {
        addComponents({
          '.btn-blue': {
            backgroundColor: 'blue',
          },
        })
      },
    ],
  }

  return run('@tailwind components', config).then((result) => {
    expect(result.css).toMatchFormattedCss(css`
      .btn-blue {
        background-color: blue;
      }
    `)
  })
})

test("plugins can apply the user's chosen prefix to components manually", () => {
  let config = {
    prefix: 'tw-',
    content: [{ raw: html`<button class="tw-btn-blue"></button>` }],
    plugins: [
      function ({ addComponents, prefix }) {
        addComponents(
          {
            [prefix('.btn-blue')]: {
              backgroundColor: 'blue',
            },
          },
          { respectPrefix: false }
        )
      },
    ],
  }

  return run('@tailwind components', config).then((result) => {
    expect(result.css).toMatchFormattedCss(css`
      .tw-btn-blue {
        background-color: blue;
      }
    `)
  })
})

test('prefix can optionally be ignored for utilities', () => {
  let config = {
    prefix: 'tw-',
    content: [{ raw: html`<div class="custom-rotate-90"></div>` }],
    plugins: [
      function ({ addUtilities }) {
        addUtilities(
          {
            '.custom-rotate-90': {
              transform: 'rotate(90deg)',
            },
          },
          { respectPrefix: false }
        )
      },
    ],
  }

  return run('@tailwind utilities', config).then((result) => {
    expect(result.css).toMatchFormattedCss(css`
      .custom-rotate-90 {
        transform: rotate(90deg);
      }
    `)
  })
})

test('important can optionally be ignored for utilities', () => {
  let config = {
    important: true,
    content: [{ raw: html`<div class="custom-rotate-90"></div>` }],
    plugins: [
      function ({ addUtilities }) {
        addUtilities(
          {
            '.custom-rotate-90': {
              transform: 'rotate(90deg)',
            },
          },
          { respectImportant: false }
        )
      },
    ],
  }

  return run('@tailwind utilities', config).then((result) => {
    expect(result.css).toMatchFormattedCss(css`
      .custom-rotate-90 {
        transform: rotate(90deg);
      }
    `)
  })
})

test('prefix will prefix all classes in a selector', () => {
  let config = {
    prefix: 'tw-',
    content: [{ raw: html`<div class="tw-btn-blue tw-w-1/4"></div>` }],
    plugins: [
      function ({ addComponents, prefix }) {
        addComponents(
          {
            [prefix('.btn-blue .w-1\\/4 > h1.text-xl + a .bar')]: {
              backgroundColor: 'blue',
            },
          },
          { respectPrefix: false }
        )
      },
    ],
  }

  return run('@tailwind components', config).then((result) => {
    expect(result.css).toMatchFormattedCss(css`
      .tw-btn-blue .tw-w-1\/4 > h1.tw-text-xl + a .tw-bar {
        background-color: blue;
      }
    `)
  })
})

test('plugins can be provided as an object with a handler function', () => {
  let config = {
    content: [
      {
        raw: html`<div class="custom-object-fill custom-object-contain custom-object-cover"></div>`,
      },
    ],
    plugins: [
      {
        handler({ addUtilities }) {
          addUtilities({
            '.custom-object-fill': {
              'object-fit': 'fill',
            },
            '.custom-object-contain': {
              'object-fit': 'contain',
            },
            '.custom-object-cover': {
              'object-fit': 'cover',
            },
          })
        },
      },
    ],
  }

  return run('@tailwind utilities', config).then((result) => {
    expect(result.css).toMatchFormattedCss(css`
      .custom-object-fill {
        object-fit: fill;
      }
      .custom-object-contain {
        object-fit: contain;
      }
      .custom-object-cover {
        object-fit: cover;
      }
    `)
  })
})

test('plugins can provide a config but no handler', () => {
  let config = {
    content: [
      {
        raw: html`<div
          class="tw-custom-object-fill tw-custom-object-contain tw-custom-object-cover"
        ></div>`,
      },
    ],
    plugins: [
      {
        config: {
          prefix: 'tw-',
        },
      },
      {
        handler({ addUtilities }) {
          addUtilities({
            '.custom-object-fill': {
              'object-fit': 'fill',
            },
            '.custom-object-contain': {
              'object-fit': 'contain',
            },
            '.custom-object-cover': {
              'object-fit': 'cover',
            },
          })
        },
      },
    ],
  }

  return run('@tailwind utilities', config).then((result) => {
    expect(result.css).toMatchFormattedCss(css`
      .tw-custom-object-fill {
        object-fit: fill;
      }
      .tw-custom-object-contain {
        object-fit: contain;
      }
      .tw-custom-object-cover {
        object-fit: cover;
      }
    `)
  })
})

test('plugins can be created using the `createPlugin` function', () => {
  let config = {
    content: [{ raw: html`<div class="test-sm test-md test-lg hover:test-sm sm:test-sm"></div>` }],
    corePlugins: [],
    theme: {
      screens: {
        sm: '400px',
      },
    },
    plugins: [
      createPlugin(
        function ({ addUtilities, theme }) {
          addUtilities(
            Object.fromEntries(
              Object.entries(theme('testPlugin')).map(([k, v]) => [
                `.test-${k}`,
                { testProperty: v },
              ])
            )
          )
        },
        {
          theme: {
            testPlugin: {
              sm: '1rem',
              md: '2rem',
              lg: '3rem',
            },
          },
        }
      ),
    ],
  }

  return run('@tailwind utilities', config).then((result) => {
    expect(result.css).toMatchFormattedCss(css`
      .test-sm {
        test-property: 1rem;
      }
      .test-md {
        test-property: 2rem;
      }
      .test-lg {
        test-property: 3rem;
      }
      .hover\:test-sm:hover {
        test-property: 1rem;
      }
      @media (min-width: 400px) {
        .sm\:test-sm {
          test-property: 1rem;
        }
      }
    `)
  })
})

test('plugins with extra options can be created using the `createPlugin.withOptions` function', () => {
  let plugin = createPlugin.withOptions(
    function ({ className }) {
      return function ({ addUtilities, theme }) {
        addUtilities(
          Object.fromEntries(
            Object.entries(theme('testPlugin')).map(([k, v]) => [
              `.${className}-${k}`,
              { testProperty: v },
            ])
          )
        )
      }
    },
    function () {
      return {
        theme: {
          testPlugin: {
            sm: '1rem',
            md: '2rem',
            lg: '3rem',
          },
        },
      }
    }
  )

  let config = {
    content: [
      { raw: html`<div class="banana-sm banana-md banana-lg hover:banana-sm sm:banana-sm"></div>` },
    ],
    corePlugins: [],
    theme: {
      screens: {
        sm: '400px',
      },
    },
    plugins: [plugin({ className: 'banana' })],
  }

  return run('@tailwind utilities', config).then((result) => {
    expect(result.css).toMatchFormattedCss(css`
      .banana-sm {
        test-property: 1rem;
      }
      .banana-md {
        test-property: 2rem;
      }
      .banana-lg {
        test-property: 3rem;
      }
      .hover\:banana-sm:hover {
        test-property: 1rem;
      }
      @media (min-width: 400px) {
        .sm\:banana-sm {
          test-property: 1rem;
        }
      }
    `)
  })
})

test('plugins should cache correctly', () => {
  let plugin = createPlugin.withOptions(({ className = 'banana' } = {}) => ({ addComponents }) => {
    addComponents({ [`.${className}`]: { position: 'absolute' } })
  })

  let config = {
    content: [{ raw: html`<div class="banana sm:banana apple sm:apple"></div>` }],
    corePlugins: [],
    theme: {
      screens: {
        sm: '400px',
      },
    },
  }

  function internalRun(options = {}) {
    return run('@tailwind components', {
      ...config,
      plugins: [plugin(options)],
    })
  }

  return Promise.all([internalRun(), internalRun({ className: 'apple' })]).then(
    ([result1, result2]) => {
      let expected1 = css`
        .banana {
          position: absolute;
        }

        @media (min-width: 400px) {
          .sm\:banana {
            position: absolute;
          }
        }
      `

      let expected2 = css`
        .apple {
          position: absolute;
        }

        @media (min-width: 400px) {
          .sm\:apple {
            position: absolute;
          }
        }
      `

      expect(result1.css).toMatchCss(expected1)
      expect(result2.css).toMatchCss(expected2)
    }
  )
})

test('plugins created using `createPlugin.withOptions` do not need to be invoked if the user wants to use the default options', () => {
  let plugin = createPlugin.withOptions(
    function ({ className } = { className: 'banana' }) {
      return function ({ addUtilities, theme }) {
        addUtilities(
          Object.fromEntries(
            Object.entries(theme('testPlugin')).map(([k, v]) => [
              `.${className}-${k}`,
              { testProperty: v },
            ])
          )
        )
      }
    },
    function () {
      return {
        theme: {
          testPlugin: {
            sm: '1rem',
            md: '2rem',
            lg: '3rem',
          },
        },
      }
    }
  )

  let config = {
    content: [
      { raw: html`<div class="banana-sm banana-md banana-lg hover:banana-sm sm:banana-sm"></div>` },
    ],
    corePlugins: [],
    theme: {
      screens: {
        sm: '400px',
      },
    },
    plugins: [plugin],
  }

  return run('@tailwind utilities', config).then((result) => {
    expect(result.css).toMatchFormattedCss(css`
      .banana-sm {
        test-property: 1rem;
      }
      .banana-md {
        test-property: 2rem;
      }
      .banana-lg {
        test-property: 3rem;
      }
      .hover\:banana-sm:hover {
        test-property: 1rem;
      }
      @media (min-width: 400px) {
        .sm\:banana-sm {
          test-property: 1rem;
        }
      }
    `)
  })
})

test('the configFunction parameter is optional when using the `createPlugin.withOptions` function', () => {
  let plugin = createPlugin.withOptions(function ({ className }) {
    return function ({ addUtilities, theme }) {
      addUtilities(
        Object.fromEntries(
          Object.entries(theme('testPlugin')).map(([k, v]) => [
            `.${className}-${k}`,
            { testProperty: v },
          ])
        )
      )
    }
  })

  let config = {
    content: [
      { raw: html`<div class="banana-sm banana-md banana-lg hover:banana-sm sm:banana-sm"></div>` },
    ],
    corePlugins: [],
    theme: {
      screens: {
        sm: '400px',
      },
      testPlugin: {
        sm: '1px',
        md: '2px',
        lg: '3px',
      },
    },
    plugins: [plugin({ className: 'banana' })],
  }

  return run('@tailwind utilities', config).then((result) => {
    expect(result.css).toMatchFormattedCss(css`
      .banana-sm {
        test-property: 1px;
      }
      .banana-md {
        test-property: 2px;
      }
      .banana-lg {
        test-property: 3px;
      }
      .hover\:banana-sm:hover {
        test-property: 1px;
      }
      @media (min-width: 400px) {
        .sm\:banana-sm {
          test-property: 1px;
        }
      }
    `)
  })
})

test('keyframes are not escaped', () => {
  let config = {
    content: [{ raw: html`<div class="foo-[abc] md:foo-[def]"></div>` }],
    corePlugins: { preflight: false },
    plugins: [
      function ({ matchUtilities }) {
        matchUtilities({
          foo: (value) => {
            return {
              [`@keyframes ${value}`]: {
                '25.001%': {
                  color: 'black',
                },
              },
              animation: `${value} 1s infinite`,
            }
          },
        })
      },
    ],
  }

  return run('@tailwind utilities', config).then((result) => {
    expect(result.css).toMatchFormattedCss(css`
      @keyframes abc {
        25.001% {
          color: black;
        }
      }

      .foo-\[abc\] {
        animation: abc 1s infinite;
      }

      @media (min-width: 768px) {
        @keyframes def {
          25.001% {
            color: black;
          }
        }

        .md\:foo-\[def\] {
          animation: def 1s infinite;
        }
      }
    `)
  })
})

test('font sizes are retrieved without default line-heights or letter-spacing using theme function', () => {
  let config = {
    content: [{ raw: html`<div class="foo"></div>` }],
    corePlugins: [],
    theme: {
      fontSize: {
        sm: ['14px', '20px'],
      },
    },
    plugins: [
      function ({ addComponents, theme }) {
        addComponents({
          '.foo': {
            fontSize: theme('fontSize.sm'),
          },
        })
      },
    ],
  }

  return run('@tailwind components', config).then((result) => {
    expect(result.css).toMatchFormattedCss(css`
      .foo {
        font-size: 14px;
      }
    `)
  })
})

test('outlines are retrieved without outline-offset using theme function', () => {
  let config = {
    content: [{ raw: html`<div class="foo"></div>` }],
    corePlugins: [],
    theme: {
      outline: {
        black: ['2px dotted black', '4px'],
      },
    },
    plugins: [
      function ({ addComponents, theme }) {
        addComponents({
          '.foo': {
            outline: theme('outline.black'),
          },
        })
      },
    ],
  }

  return run('@tailwind components', config).then((result) => {
    expect(result.css).toMatchFormattedCss(css`
      .foo {
        outline: 2px dotted black;
      }
    `)
  })
})

test('box-shadow values are joined when retrieved using the theme function', () => {
  let config = {
    content: [{ raw: html`<div class="foo"></div>` }],
    corePlugins: [],
    theme: {
      boxShadow: {
        lol: ['width', 'height'],
      },
    },
    plugins: [
      function ({ addComponents, theme }) {
        addComponents({
          '.foo': {
            boxShadow: theme('boxShadow.lol'),
          },
        })
      },
    ],
  }

  return run('@tailwind components', config).then((result) => {
    expect(result.css).toMatchFormattedCss(css`
      .foo {
        box-shadow: width, height;
      }
    `)
  })
})

test('transition-property values are joined when retrieved using the theme function', () => {
  let config = {
    content: [{ raw: html`<div class="foo"></div>` }],
    corePlugins: [],
    theme: {
      transitionProperty: {
        lol: ['width', 'height'],
      },
    },
    plugins: [
      function ({ addComponents, theme }) {
        addComponents({
          '.foo': {
            transitionProperty: theme('transitionProperty.lol'),
          },
        })
      },
    ],
  }

  return run('@tailwind components', config).then((result) => {
    expect(result.css).toMatchFormattedCss(css`
      .foo {
        transition-property: width, height;
      }
    `)
  })
})

test('transition-duration values are joined when retrieved using the theme function', () => {
  let config = {
    content: [{ raw: html`<div class="foo"></div>` }],
    corePlugins: [],
    theme: {
      transitionDuration: {
        lol: ['width', 'height'],
      },
    },
    plugins: [
      function ({ addComponents, theme }) {
        addComponents({
          '.foo': {
            transitionDuration: theme('transitionDuration.lol'),
          },
        })
      },
    ],
  }

  return run('@tailwind components', config).then((result) => {
    expect(result.css).toMatchFormattedCss(css`
      .foo {
        transition-duration: width, height;
      }
    `)
  })
})

test('transition-delay values are joined when retrieved using the theme function', () => {
  let config = {
    content: [{ raw: html`<div class="foo"></div>` }],
    corePlugins: [],
    theme: {
      transitionDuration: {
        lol: ['width', 'height'],
      },
    },
    plugins: [
      function ({ addComponents, theme }) {
        addComponents({
          '.foo': {
            transitionDuration: theme('transitionDuration.lol'),
          },
        })
      },
    ],
  }

  return run('@tailwind components', config).then((result) => {
    expect(result.css).toMatchFormattedCss(css`
      .foo {
        transition-duration: width, height;
      }
    `)
  })
})

test('transition-timing-function values are joined when retrieved using the theme function', () => {
  let config = {
    content: [{ raw: html`<div class="foo"></div>` }],
    corePlugins: [],
    theme: {
      transitionTimingFunction: {
        lol: ['width', 'height'],
      },
    },
    plugins: [
      function ({ addComponents, theme }) {
        addComponents({
          '.foo': {
            transitionTimingFunction: theme('transitionTimingFunction.lol'),
          },
        })
      },
    ],
  }

  return run('@tailwind components', config).then((result) => {
    expect(result.css).toMatchFormattedCss(css`
      .foo {
        transition-timing-function: width, height;
      }
    `)
  })
})

test('background-image values are joined when retrieved using the theme function', () => {
  let config = {
    content: [{ raw: html`<div class="foo"></div>` }],
    corePlugins: [],
    theme: {
      backgroundImage: {
        lol: ['width', 'height'],
      },
    },
    plugins: [
      function ({ addComponents, theme }) {
        addComponents({
          '.foo': {
            backgroundImage: theme('backgroundImage.lol'),
          },
        })
      },
    ],
  }

  return run('@tailwind components', config).then((result) => {
    expect(result.css).toMatchFormattedCss(css`
      .foo {
        background-image: width, height;
      }
    `)
  })
})

test('background-size values are joined when retrieved using the theme function', () => {
  let config = {
    content: [{ raw: html`<div class="foo"></div>` }],
    corePlugins: [],
    theme: {
      backgroundSize: {
        lol: ['width', 'height'],
      },
    },
    plugins: [
      function ({ addComponents, theme }) {
        addComponents({
          '.foo': {
            backgroundSize: theme('backgroundSize.lol'),
          },
        })
      },
    ],
  }

  return run('@tailwind components', config).then((result) => {
    expect(result.css).toMatchFormattedCss(css`
      .foo {
        background-size: width, height;
      }
    `)
  })
})

test('background-color values are joined when retrieved using the theme function', () => {
  let config = {
    content: [{ raw: html`<div class="foo"></div>` }],
    corePlugins: [],
    theme: {
      backgroundColor: {
        lol: ['width', 'height'],
      },
    },
    plugins: [
      function ({ addComponents, theme }) {
        addComponents({
          '.foo': {
            backgroundColor: theme('backgroundColor.lol'),
          },
        })
      },
    ],
  }

  return run('@tailwind components', config).then((result) => {
    expect(result.css).toMatchFormattedCss(css`
      .foo {
        background-color: width, height;
      }
    `)
  })
})

test('cursor values are joined when retrieved using the theme function', () => {
  let config = {
    content: [{ raw: html`<div class="foo"></div>` }],
    corePlugins: [],
    theme: {
      cursor: {
        lol: ['width', 'height'],
      },
    },
    plugins: [
      function ({ addComponents, theme }) {
        addComponents({
          '.foo': {
            cursor: theme('cursor.lol'),
          },
        })
      },
    ],
  }

  return run('@tailwind components', config).then((result) => {
    expect(result.css).toMatchFormattedCss(css`
      .foo {
        cursor: width, height;
      }
    `)
  })
})

test('animation values are joined when retrieved using the theme function', () => {
  let config = {
    content: [{ raw: html`<div class="foo"></div>` }],
    corePlugins: [],
    theme: {
      animation: {
        lol: ['width', 'height'],
      },
    },
    plugins: [
      function ({ addComponents, theme }) {
        addComponents({
          '.foo': {
            animation: theme('animation.lol'),
          },
        })
      },
    ],
  }

  return run('@tailwind components', config).then((result) => {
    expect(result.css).toMatchFormattedCss(css`
      .foo {
        animation: width, height;
      }
    `)
  })
})

test('custom properties are not converted to kebab-case when added to base layer', () => {
  let config = {
    content: [],
    plugins: [
      function ({ addBase }) {
        addBase({
          ':root': {
            '--colors-primaryThing-500': '0, 0, 255',
          },
        })
      },
    ],
  }

  return run('@tailwind base', config).then((result) => {
    expect(result.css).toContain(`--colors-primaryThing-500: 0, 0, 255;`)
  })
})