const path = require('path') const tailwind = require('tailwindcss') const postcss = require('postcss') const typographyPlugin = require('.') let html = String.raw let css = String.raw let vars = ` --tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-translate-x: 0; --tw-translate-y: 0; --tw-rotate: 0; --tw-skew-x: 0; --tw-skew-y: 0; --tw-scale-x: 1; --tw-scale-y: 1; --tw-pan-x: ; --tw-pan-y: ; --tw-pinch-zoom: ; --tw-scroll-snap-strictness: proximity; --tw-ordinal: ; --tw-slashed-zero: ; --tw-numeric-figure: ; --tw-numeric-spacing: ; --tw-numeric-fraction: ; --tw-ring-inset: ; --tw-ring-offset-width: 0px; --tw-ring-offset-color: #fff; --tw-ring-color: rgb(59 130 246 / 0.5); --tw-ring-offset-shadow: 0 0 #0000; --tw-ring-shadow: 0 0 #0000; --tw-shadow: 0 0 #0000; --tw-shadow-colored: 0 0 #0000; --tw-blur: ; --tw-brightness: ; --tw-contrast: ; --tw-grayscale: ; --tw-hue-rotate: ; --tw-invert: ; --tw-saturate: ; --tw-sepia: ; --tw-drop-shadow: ; --tw-backdrop-blur: ; --tw-backdrop-brightness: ; --tw-backdrop-contrast: ; --tw-backdrop-grayscale: ; --tw-backdrop-hue-rotate: ; --tw-backdrop-invert: ; --tw-backdrop-opacity: ; --tw-backdrop-saturate: ; --tw-backdrop-sepia: ; ` let defaults = css` *, ::before, ::after { ${vars} } ::backdrop { ${vars} } ` function run(config, plugin = tailwind) { let { currentTestName } = expect.getState() config = { ...{ plugins: [typographyPlugin], corePlugins: { preflight: false } }, ...config, } return postcss(plugin(config)).process( ['@tailwind base;', '@tailwind components;', '@tailwind utilities'].join('\n'), { from: `${path.resolve(__filename)}?test=${currentTestName}`, } ) } test('specificity is reduced with :where', async () => { let config = { content: [{ raw: html`
` }], theme: { typography: { DEFAULT: { css: [ { color: 'var(--tw-prose-body)', maxWidth: '65ch', '[class~="lead"]': { color: 'var(--tw-prose-lead)', }, strong: { color: 'var(--tw-prose-bold)', fontWeight: '600', }, 'ol[type="A"]': { listStyleType: 'upper-alpha', }, 'blockquote p:first-of-type::before': { content: 'open-quote', }, 'blockquote p:last-of-type::after': { content: 'close-quote', }, 'h4 strong': { fontWeight: '700', }, 'figure > *': { margin: 0, }, 'ol > li::marker': { fontWeight: '400', color: 'var(--tw-prose-counters)', }, '> ul > li p': { marginTop: '16px', marginBottom: '16px', }, 'code::before': { content: '"`"', }, 'code::after': { content: '"`"', }, }, ], }, }, }, } return run(config).then((result) => { expect(result.css).toMatchFormattedCss( css` ${defaults} .prose { color: var(--tw-prose-body); max-width: 65ch; } .prose :where([class~='lead']):not(:where([class~='not-prose'], [class~='not-prose'] *)) { color: var(--tw-prose-lead); } .prose :where(strong):not(:where([class~='not-prose'], [class~='not-prose'] *)) { color: var(--tw-prose-bold); font-weight: 600; } .prose :where(ol[type='A']):not(:where([class~='not-prose'], [class~='not-prose'] *)) { list-style-type: upper-alpha; } .prose :where(blockquote p:first-of-type):not(:where([class~='not-prose'], [class~='not-prose'] *))::before { content: open-quote; } .prose :where(blockquote p:last-of-type):not(:where([class~='not-prose'], [class~='not-prose'] *))::after { content: close-quote; } .prose :where(h4 strong):not(:where([class~='not-prose'], [class~='not-prose'] *)) { font-weight: 700; } .prose :where(figure > *):not(:where([class~='not-prose'], [class~='not-prose'] *)) { margin: 0; } .prose :where(ol > li):not(:where([class~='not-prose'], [class~='not-prose'] *))::marker { font-weight: 400; color: var(--tw-prose-counters); } .prose :where(.prose > ul > li p):not(:where([class~='not-prose'], [class~='not-prose'] *)) { margin-top: 16px; margin-bottom: 16px; } .prose :where(code):not(:where([class~='not-prose'], [class~='not-prose'] *))::before { content: '`'; } .prose :where(code):not(:where([class~='not-prose'], [class~='not-prose'] *))::after { content: '`'; } ` ) }) }) test('variants', async () => { let config = { content: [{ raw: html`
` }], theme: { typography: { DEFAULT: { css: [ { color: 'red', p: { color: 'lime', }, '> ul > li': { color: 'purple', }, }, ], }, lg: { css: { color: 'green', p: { color: 'tomato', }, '> ul > li': { color: 'blue', }, }, }, xl: { css: { color: 'yellow', '> ul > li': { color: 'hotpink', }, }, }, }, }, } return run(config).then((result) => { expect(result.css).toMatchFormattedCss( css` ${defaults} .hover\:prose-lg:hover { color: green; } .hover\:prose-lg:hover :where(p):not(:where([class~='not-prose'], [class~='not-prose'] *)) { color: tomato; } .hover\:prose-lg:hover :where(.hover\:prose-lg:hover > ul > li):not(:where([class~='not-prose'], [class~='not-prose'] *)) { color: blue; } @media (min-width: 640px) { .sm\:prose { color: red; } .sm\:prose :where(p):not(:where([class~='not-prose'], [class~='not-prose'] *)) { color: lime; } .sm\:prose :where(.sm\:prose > ul > li):not(:where([class~='not-prose'], [class~='not-prose'] *)) { color: purple; } } @media (min-width: 1024px) { .lg\:prose-lg { color: green; } .lg\:prose-lg :where(p):not(:where([class~='not-prose'], [class~='not-prose'] *)) { color: tomato; } .lg\:prose-lg :where(.lg\:prose-lg > ul > li):not(:where([class~='not-prose'], [class~='not-prose'] *)) { color: blue; } } ` ) }) }) test('modifiers', async () => { let config = { content: [{ raw: html`
` }], theme: { typography: { DEFAULT: { css: [ { color: 'var(--tw-prose-body)', maxWidth: '65ch', '[class~="lead"]': { color: 'var(--tw-prose-lead)', }, strong: { color: 'var(--tw-prose-bold)', fontWeight: '600', }, 'ol[type="A"]': { listStyleType: 'upper-alpha', }, 'blockquote p:first-of-type::before': { content: 'open-quote', }, 'blockquote p:last-of-type::after': { content: 'close-quote', }, 'h4 strong': { fontWeight: '700', }, 'figure > *': { margin: 0, }, 'ol > li::marker': { fontWeight: '400', color: 'var(--tw-prose-counters)', }, 'code::before': { content: '"`"', }, 'code::after': { content: '"`"', }, }, ], }, lg: { css: [ { fontSize: '18px', lineHeight: '1.75', p: { marginTop: '24px', marginBottom: '24px', }, '[class~="lead"]': { fontSize: '22px', }, blockquote: { marginTop: '40px', marginBottom: '40px', }, '> ul > li': { paddingLeft: '12px', }, h1: { fontSize: '48px', marginTop: '0', marginBottom: '40px', }, h2: { fontSize: '30px', marginTop: '56px', marginBottom: '32px', }, h3: { fontSize: '24px', marginTop: '40px', marginBottom: '16px', }, }, ], }, }, }, } return run(config).then((result) => { expect(result.css).toMatchFormattedCss( css` ${defaults} .prose { color: var(--tw-prose-body); max-width: 65ch; } .prose :where([class~='lead']):not(:where([class~='not-prose'], [class~='not-prose'] *)) { color: var(--tw-prose-lead); } .prose :where(strong):not(:where([class~='not-prose'], [class~='not-prose'] *)) { color: var(--tw-prose-bold); font-weight: 600; } .prose :where(ol[type='A']):not(:where([class~='not-prose'], [class~='not-prose'] *)) { list-style-type: upper-alpha; } .prose :where(blockquote p:first-of-type):not(:where([class~='not-prose'], [class~='not-prose'] *))::before { content: open-quote; } .prose :where(blockquote p:last-of-type):not(:where([class~='not-prose'], [class~='not-prose'] *))::after { content: close-quote; } .prose :where(h4 strong):not(:where([class~='not-prose'], [class~='not-prose'] *)) { font-weight: 700; } .prose :where(figure > *):not(:where([class~='not-prose'], [class~='not-prose'] *)) { margin: 0; } .prose :where(ol > li):not(:where([class~='not-prose'], [class~='not-prose'] *))::marker { font-weight: 400; color: var(--tw-prose-counters); } .prose :where(code):not(:where([class~='not-prose'], [class~='not-prose'] *))::before { content: '`'; } .prose :where(code):not(:where([class~='not-prose'], [class~='not-prose'] *))::after { content: '`'; } .prose-lg { font-size: 18px; line-height: 1.75; } .prose-lg :where(p):not(:where([class~='not-prose'], [class~='not-prose'] *)) { margin-top: 24px; margin-bottom: 24px; } .prose-lg :where([class~='lead']):not(:where([class~='not-prose'], [class~='not-prose'] *)) { font-size: 22px; } .prose-lg :where(blockquote):not(:where([class~='not-prose'], [class~='not-prose'] *)) { margin-top: 40px; margin-bottom: 40px; } .prose-lg :where(.prose-lg > ul > li):not(:where([class~='not-prose'], [class~='not-prose'] *)) { padding-left: 12px; } .prose-lg :where(h1):not(:where([class~='not-prose'], [class~='not-prose'] *)) { font-size: 48px; margin-top: 0; margin-bottom: 40px; } .prose-lg :where(h2):not(:where([class~='not-prose'], [class~='not-prose'] *)) { font-size: 30px; margin-top: 56px; margin-bottom: 32px; } .prose-lg :where(h3):not(:where([class~='not-prose'], [class~='not-prose'] *)) { font-size: 24px; margin-top: 40px; margin-bottom: 16px; } ` ) }) }) test('legacy target', async () => { let config = { plugins: [typographyPlugin({ target: 'legacy' })], content: [ { raw: html`
` }, ], theme: { typography: { DEFAULT: { css: [ { color: 'var(--tw-prose-body)', maxWidth: '65ch', '[class~="lead"]': { color: 'var(--tw-prose-lead)', }, strong: { color: 'var(--tw-prose-bold)', fontWeight: '600', }, 'ol[type="A"]': { listStyleType: 'upper-alpha', }, 'blockquote p:first-of-type::before': { content: 'open-quote', }, 'blockquote p:last-of-type::after': { content: 'close-quote', }, 'h4 strong': { fontWeight: '700', }, 'figure > *': { margin: 0, }, 'ol > li::marker': { fontWeight: '400', color: 'var(--tw-prose-counters)', }, 'code::before': { content: '"`"', }, 'code::after': { content: '"`"', }, }, ], }, }, }, } return run(config).then((result) => { expect(result.css).toMatchFormattedCss( css` ${defaults} .prose { color: var(--tw-prose-body); max-width: 65ch; } .prose [class~='lead'] { color: var(--tw-prose-lead); } .prose strong { color: var(--tw-prose-bold); font-weight: 600; } .prose ol[type='A'] { list-style-type: upper-alpha; } .prose blockquote p:first-of-type::before { content: open-quote; } .prose blockquote p:last-of-type::after { content: close-quote; } .prose h4 strong { font-weight: 700; } .prose figure > * { margin: 0; } .prose ol > li::marker { font-weight: 400; color: var(--tw-prose-counters); } .prose code::before { content: '`'; } .prose code::after { content: '`'; } .prose-headings\:text-ellipsis h1 { text-overflow: ellipsis; } .prose-headings\:text-ellipsis h2 { text-overflow: ellipsis; } .prose-headings\:text-ellipsis h3 { text-overflow: ellipsis; } .prose-headings\:text-ellipsis h4 { text-overflow: ellipsis; } .prose-headings\:text-ellipsis h5 { text-overflow: ellipsis; } .prose-headings\:text-ellipsis h6 { text-overflow: ellipsis; } .prose-headings\:text-ellipsis th { text-overflow: ellipsis; } .prose-h1\:text-center h1 { text-align: center; } ` ) }) }) test('custom class name', async () => { let config = { plugins: [typographyPlugin({ className: 'markdown' })], content: [{ raw: html`
` }], theme: { typography: { DEFAULT: { css: [ { color: 'var(--tw-prose-body)', maxWidth: '65ch', '[class~="lead"]': { color: 'var(--tw-prose-lead)', }, strong: { color: 'var(--tw-prose-bold)', fontWeight: '600', }, 'ol[type="A"]': { listStyleType: 'upper-alpha', }, 'blockquote p:first-of-type::before': { content: 'open-quote', }, 'blockquote p:last-of-type::after': { content: 'close-quote', }, 'h4 strong': { fontWeight: '700', }, 'figure > *': { margin: 0, }, 'ol > li::marker': { fontWeight: '400', color: 'var(--tw-prose-counters)', }, 'code::before': { content: '"`"', }, 'code::after': { content: '"`"', }, }, ], }, }, }, } return run(config).then((result) => { expect(result.css).toMatchFormattedCss( css` ${defaults} .markdown { color: var(--tw-prose-body); max-width: 65ch; } .markdown :where([class~='lead']):not(:where([class~='not-markdown'], [class~='not-markdown'] *)) { color: var(--tw-prose-lead); } .markdown :where(strong):not(:where([class~='not-markdown'], [class~='not-markdown'] *)) { color: var(--tw-prose-bold); font-weight: 600; } .markdown :where(ol[type='A']):not(:where([class~='not-markdown'], [class~='not-markdown'] *)) { list-style-type: upper-alpha; } .markdown :where(blockquote p:first-of-type):not(:where([class~='not-markdown'], [class~='not-markdown'] *))::before { content: open-quote; } .markdown :where(blockquote p:last-of-type):not(:where([class~='not-markdown'], [class~='not-markdown'] *))::after { content: close-quote; } .markdown :where(h4 strong):not(:where([class~='not-markdown'], [class~='not-markdown'] *)) { font-weight: 700; } .markdown :where(figure > *):not(:where([class~='not-markdown'], [class~='not-markdown'] *)) { margin: 0; } .markdown :where(ol > li):not(:where([class~='not-markdown'], [class~='not-markdown'] *))::marker { font-weight: 400; color: var(--tw-prose-counters); } .markdown :where(code):not(:where([class~='not-markdown'], [class~='not-markdown'] *))::before { content: '`'; } .markdown :where(code):not(:where([class~='not-markdown'], [class~='not-markdown'] *))::after { content: '`'; } ` ) }) }) test('element variants', async () => { let config = { content: [ { raw: html`
`, }, ], theme: { typography: { DEFAULT: { css: [ { color: 'var(--tw-prose-body)', '[class~="lead"]': { color: 'var(--tw-prose-lead)', }, strong: { color: 'var(--tw-prose-bold)', fontWeight: '600', }, 'h4 strong': { fontWeight: '700', }, }, ], }, }, }, } return run(config).then((result) => { expect(result.css).toMatchFormattedCss( css` ${defaults} .prose { color: var(--tw-prose-body); } .prose :where([class~='lead']):not(:where([class~='not-prose'], [class~='not-prose'] *)) { color: var(--tw-prose-lead); } .prose :where(strong):not(:where([class~='not-prose'], [class~='not-prose'] *)) { color: var(--tw-prose-bold); font-weight: 600; } .prose :where(h4 strong):not(:where([class~='not-prose'], [class~='not-prose'] *)) { font-weight: 700; } .prose-headings\:underline :is(:where(h1, h2, h3, h4, h5, h6, th):not(:where([class~='not-prose'], [class~='not-prose'] *))) { text-decoration-line: underline; } .prose-h1\:text-3xl :is(:where(h1):not(:where([class~='not-prose'], [class~='not-prose'] *))) { font-size: 1.875rem; line-height: 2.25rem; } .prose-h2\:text-2xl :is(:where(h2):not(:where([class~='not-prose'], [class~='not-prose'] *))) { font-size: 1.5rem; line-height: 2rem; } .prose-h3\:text-xl :is(:where(h3):not(:where([class~='not-prose'], [class~='not-prose'] *))) { font-size: 1.25rem; line-height: 1.75rem; } .prose-h4\:text-lg :is(:where(h4):not(:where([class~='not-prose'], [class~='not-prose'] *))) { font-size: 1.125rem; line-height: 1.75rem; } .prose-p\:text-gray-700 :is(:where(p):not(:where([class~='not-prose'], [class~='not-prose'] *))) { --tw-text-opacity: 1; color: rgb(55 65 81 / var(--tw-text-opacity)); } .prose-a\:font-bold :is(:where(a):not(:where([class~='not-prose'], [class~='not-prose'] *))) { font-weight: 700; } .prose-blockquote\:italic :is(:where(blockquote):not(:where([class~='not-prose'], [class~='not-prose'] *))) { font-style: italic; } .prose-figure\:mx-auto :is(:where(figure):not(:where([class~='not-prose'], [class~='not-prose'] *))) { margin-left: auto; margin-right: auto; } .prose-figcaption\:opacity-75 :is(:where(figcaption):not(:where([class~='not-prose'], [class~='not-prose'] *))) { opacity: 0.75; } .prose-strong\:font-medium :is(:where(strong):not(:where([class~='not-prose'], [class~='not-prose'] *))) { font-weight: 500; } .prose-em\:italic :is(:where(em):not(:where([class~='not-prose'], [class~='not-prose'] *))) { font-style: italic; } .prose-code\:font-mono :is(:where(code):not(:where([class~='not-prose'], [class~='not-prose'] *))) { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace; } .prose-pre\:font-mono :is(:where(pre):not(:where([class~='not-prose'], [class~='not-prose'] *))) { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace; } .prose-ol\:pl-6 :is(:where(ol):not(:where([class~='not-prose'], [class~='not-prose'] *))) { padding-left: 1.5rem; } .prose-ul\:pl-8 :is(:where(ul):not(:where([class~='not-prose'], [class~='not-prose'] *))) { padding-left: 2rem; } .prose-li\:my-4 :is(:where(li):not(:where([class~='not-prose'], [class~='not-prose'] *))) { margin-top: 1rem; margin-bottom: 1rem; } .prose-table\:my-8 :is(:where(table):not(:where([class~='not-prose'], [class~='not-prose'] *))) { margin-top: 2rem; margin-bottom: 2rem; } .prose-thead\:border-red-300 :is(:where(thead):not(:where([class~='not-prose'], [class~='not-prose'] *))) { --tw-border-opacity: 1; border-color: rgb(252 165 165 / var(--tw-border-opacity)); } .prose-tr\:border-red-200 :is(:where(tr):not(:where([class~='not-prose'], [class~='not-prose'] *))) { --tw-border-opacity: 1; border-color: rgb(254 202 202 / var(--tw-border-opacity)); } .prose-th\:text-left :is(:where(th):not(:where([class~='not-prose'], [class~='not-prose'] *))) { text-align: left; } .prose-img\:rounded-lg :is(:where(img):not(:where([class~='not-prose'], [class~='not-prose'] *))) { border-radius: 0.5rem; } .prose-video\:my-12 :is(:where(video):not(:where([class~='not-prose'], [class~='not-prose'] *))) { margin-top: 3rem; margin-bottom: 3rem; } .prose-hr\:border-t-2 :is(:where(hr):not(:where([class~='not-prose'], [class~='not-prose'] *))) { border-top-width: 2px; } .prose-lead\:italic :is(:where([class~='lead']):not(:where([class~='not-prose'], [class~='not-prose'] *))) { font-style: italic; } ` ) }) }) test('element variants with custom class name', async () => { let config = { plugins: [typographyPlugin({ className: 'markdown' })], content: [ { raw: html`
`, }, ], theme: { typography: { DEFAULT: { css: [ { color: 'var(--tw-prose-body)', '[class~="lead"]': { color: 'var(--tw-prose-lead)', }, strong: { color: 'var(--tw-prose-bold)', fontWeight: '600', }, 'h4 strong': { fontWeight: '700', }, }, ], }, }, }, } return run(config).then((result) => { expect(result.css).toMatchFormattedCss( css` ${defaults} .markdown { color: var(--tw-prose-body); } .markdown :where([class~='lead']):not(:where([class~='not-markdown'], [class~='not-markdown'] *)) { color: var(--tw-prose-lead); } .markdown :where(strong):not(:where([class~='not-markdown'], [class~='not-markdown'] *)) { color: var(--tw-prose-bold); font-weight: 600; } .markdown :where(h4 strong):not(:where([class~='not-markdown'], [class~='not-markdown'] *)) { font-weight: 700; } .markdown-headings\:underline :is(:where(h1, h2, h3, h4, h5, h6, th):not(:where([class~='not-markdown'], [class~='not-markdown'] *))) { text-decoration-line: underline; } .markdown-h1\:text-3xl :is(:where(h1):not(:where([class~='not-markdown'], [class~='not-markdown'] *))) { font-size: 1.875rem; line-height: 2.25rem; } .markdown-h2\:text-2xl :is(:where(h2):not(:where([class~='not-markdown'], [class~='not-markdown'] *))) { font-size: 1.5rem; line-height: 2rem; } .markdown-h3\:text-xl :is(:where(h3):not(:where([class~='not-markdown'], [class~='not-markdown'] *))) { font-size: 1.25rem; line-height: 1.75rem; } .markdown-h4\:text-lg :is(:where(h4):not(:where([class~='not-markdown'], [class~='not-markdown'] *))) { font-size: 1.125rem; line-height: 1.75rem; } .markdown-p\:text-gray-700 :is(:where(p):not(:where([class~='not-markdown'], [class~='not-markdown'] *))) { --tw-text-opacity: 1; color: rgb(55 65 81 / var(--tw-text-opacity)); } .markdown-a\:font-bold :is(:where(a):not(:where([class~='not-markdown'], [class~='not-markdown'] *))) { font-weight: 700; } .markdown-blockquote\:italic :is(:where(blockquote):not(:where([class~='not-markdown'], [class~='not-markdown'] *))) { font-style: italic; } .markdown-figure\:mx-auto :is(:where(figure):not(:where([class~='not-markdown'], [class~='not-markdown'] *))) { margin-left: auto; margin-right: auto; } .markdown-figcaption\:opacity-75 :is(:where(figcaption):not(:where([class~='not-markdown'], [class~='not-markdown'] *))) { opacity: 0.75; } .markdown-strong\:font-medium :is(:where(strong):not(:where([class~='not-markdown'], [class~='not-markdown'] *))) { font-weight: 500; } .markdown-em\:italic :is(:where(em):not(:where([class~='not-markdown'], [class~='not-markdown'] *))) { font-style: italic; } .markdown-code\:font-mono :is(:where(code):not(:where([class~='not-markdown'], [class~='not-markdown'] *))) { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace; } .markdown-pre\:font-mono :is(:where(pre):not(:where([class~='not-markdown'], [class~='not-markdown'] *))) { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace; } .markdown-ol\:pl-6 :is(:where(ol):not(:where([class~='not-markdown'], [class~='not-markdown'] *))) { padding-left: 1.5rem; } .markdown-ul\:pl-8 :is(:where(ul):not(:where([class~='not-markdown'], [class~='not-markdown'] *))) { padding-left: 2rem; } .markdown-li\:my-4 :is(:where(li):not(:where([class~='not-markdown'], [class~='not-markdown'] *))) { margin-top: 1rem; margin-bottom: 1rem; } .markdown-table\:my-8 :is(:where(table):not(:where([class~='not-markdown'], [class~='not-markdown'] *))) { margin-top: 2rem; margin-bottom: 2rem; } .markdown-thead\:border-red-300 :is(:where(thead):not(:where([class~='not-markdown'], [class~='not-markdown'] *))) { --tw-border-opacity: 1; border-color: rgb(252 165 165 / var(--tw-border-opacity)); } .markdown-tr\:border-red-200 :is(:where(tr):not(:where([class~='not-markdown'], [class~='not-markdown'] *))) { --tw-border-opacity: 1; border-color: rgb(254 202 202 / var(--tw-border-opacity)); } .markdown-th\:text-left :is(:where(th):not(:where([class~='not-markdown'], [class~='not-markdown'] *))) { text-align: left; } .markdown-img\:rounded-lg :is(:where(img):not(:where([class~='not-markdown'], [class~='not-markdown'] *))) { border-radius: 0.5rem; } .markdown-video\:my-12 :is(:where(video):not(:where([class~='not-markdown'], [class~='not-markdown'] *))) { margin-top: 3rem; margin-bottom: 3rem; } .markdown-hr\:border-t-2 :is(:where(hr):not(:where([class~='not-markdown'], [class~='not-markdown'] *))) { border-top-width: 2px; } .markdown-lead\:italic :is(:where([class~='lead']):not(:where([class~='not-markdown'], [class~='not-markdown'] *))) { font-style: italic; } ` ) }) }) test('customizing defaults with multiple values does not result in invalid css', async () => { let config = { plugins: [typographyPlugin()], content: [ { raw: html`
`, }, ], theme: { typography: { DEFAULT: { css: { textAlign: ['-webkit-match-parent', 'match-parent'], }, }, }, }, } return run(config).then((result) => { expect(result.css).toMatchFormattedCss( css` ${defaults} .prose { text-align: -webkit-match-parent; text-align: match-parent; } ` ) }) }) it('should be possible to use nested syntax (&) when extending the config', () => { let config = { plugins: [typographyPlugin()], content: [ { raw: html`
`, }, ], theme: { extend: { typography: { DEFAULT: { css: { color: '#000', a: { color: '#888', '&:hover': { color: '#ff0000', }, }, }, }, }, }, }, } return run(config).then((result) => { expect(result.css).toIncludeCss(css` .prose { color: #000; max-width: 65ch; } `) expect(result.css).toIncludeCss(css` .prose :where(a):not(:where([class~='not-prose'], [class~='not-prose'] *)) { color: #888; text-decoration: underline; font-weight: 500; } `) expect(result.css).toIncludeCss(css` .prose :where(a):not(:where([class~='not-prose'], [class~='not-prose'] *)):hover { color: #ff0000; } `) }) }) it('should be possible to specify custom h5 and h6 styles', () => { let config = { plugins: [typographyPlugin()], content: [ { raw: html`
`, }, ], } return run(config).then((result) => { expect(result.css).toIncludeCss(css` .prose-h5\:text-sm :is(:where(h5):not(:where([class~='not-prose'], [class~='not-prose'] *))) { font-size: 0.875rem; line-height: 1.25rem; } .prose-h6\:text-xl :is(:where(h6):not(:where([class~='not-prose'], [class~='not-prose'] *))) { font-size: 1.25rem; line-height: 1.75rem; } `) }) }) it('should not break with multiple selectors with pseudo elements using variants', () => { let config = { darkMode: 'class', plugins: [typographyPlugin()], content: [ { raw: html`
`, }, ], theme: { typography: { DEFAULT: { css: { 'ol li::before, ul li::before': { color: 'red', }, }, }, }, }, } return run(config).then((result) => { expect(result.css).toIncludeCss(css` .dark .dark\:prose :where(ol li, ul li):not(:where([class~='not-prose'], [class~='not-prose'] *))::before { color: red; } `) }) }) it('lifts all common, trailing pseudo elements when the same across all selectors', () => { let config = { darkMode: 'class', plugins: [typographyPlugin()], content: [ { raw: html`
`, }, ], theme: { typography: { DEFAULT: { css: { 'ol li::marker::before, ul li::marker::before': { color: 'red', }, }, }, }, }, } return run(config).then((result) => { expect(result.css).toIncludeCss(css` .prose :where(ol li, ul li):not(:where([class~='not-prose'], [class~='not-prose'] *))::marker::before { color: red; } `) // TODO: The output here is a bug in tailwindcss variant selector rewriting // IT should be ::marker::before expect(result.css).toIncludeCss(css` .dark .dark\:prose :where(ol li, ul li):not(:where([class~='not-prose'], [class~='not-prose'] *))::before::marker { color: red; } `) }) }) it('does not modify selectors with differing pseudo elements', () => { let config = { darkMode: 'class', plugins: [typographyPlugin()], content: [ { raw: html`
`, }, ], theme: { typography: { DEFAULT: { css: { 'ol li::before, ul li::after': { color: 'red', }, }, }, }, }, } return run(config).then((result) => { expect(result.css).toIncludeCss(css` .prose :where(ol li::before, ul li::after):not(:where([class~='not-prose'], [class~='not-prose'] *)) { color: red; } `) // TODO: The output here is a bug in tailwindcss variant selector rewriting expect(result.css).toIncludeCss(css` .dark .dark\:prose :where(ol li, ul li):not(:where([class~='not-prose'], [class~='not-prose'] *))::before, ::after { color: red; } `) }) }) it('lifts only the common, trailing pseudo elements from selectors', () => { let config = { darkMode: 'class', plugins: [typographyPlugin()], content: [ { raw: html`
`, }, ], theme: { typography: { DEFAULT: { css: { 'ol li::scroll-thumb::before, ul li::scroll-track::before': { color: 'red', }, }, }, }, }, } return run(config).then((result) => { expect(result.css).toIncludeCss(css` .prose :where(ol li::scroll-thumb, ul li::scroll-track):not(:where([class~='not-prose'], [class~='not-prose'] *))::before { color: red; } `) // TODO: The output here is a bug in tailwindcss variant selector rewriting expect(result.css).toIncludeCss(css` .dark .dark\:prose :where(ol li, ul li):not(:where([class~='not-prose'], [class~='not-prose'] *))::scroll-thumb, ::scroll-track, ::before { color: red; } `) }) }) it('ignores common non-trailing pseudo-elements in selectors', () => { let config = { darkMode: 'class', plugins: [typographyPlugin()], content: [ { raw: html`
`, }, ], theme: { typography: { DEFAULT: { css: { 'ol li::before::scroll-thumb, ul li::before::scroll-track': { color: 'red', }, }, }, }, }, } return run(config).then((result) => { expect(result.css).toIncludeCss(css` .prose :where(ol li::before::scroll-thumb, ul li::before::scroll-track):not(:where([class~='not-prose'], [class~='not-prose'] *)) { color: red; } `) // TODO: The output here is a bug in tailwindcss variant selector rewriting expect(result.css).toIncludeCss(css` .dark .dark\:prose :where(ol li::scroll-thumb, ul li::scroll-track):not(:where([class~='not-prose'], [class~='not-prose'] *))::before, ::before { color: red; } `) }) }) test('lead styles are inserted after paragraph styles', async () => { let config = { content: [{ raw: html`
` }], } return run(config).then((result) => { expect(result.css).toIncludeCss( css` .prose { color: var(--tw-prose-body); max-width: 65ch; } .prose :where(p):not(:where([class~='not-prose'], [class~='not-prose'] *)) { margin-top: 1.25em; margin-bottom: 1.25em; } .prose :where([class~='lead']):not(:where([class~='not-prose'], [class~='not-prose'] *)) { color: var(--tw-prose-lead); font-size: 1.25em; line-height: 1.6; margin-top: 1.2em; margin-bottom: 1.2em; } ` ) }) })