Skip to content

Commit

Permalink
x
Browse files Browse the repository at this point in the history
  • Loading branch information
ambar committed Apr 19, 2024
1 parent b2ffda5 commit 9a71a18
Show file tree
Hide file tree
Showing 3 changed files with 135 additions and 158 deletions.
37 changes: 35 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type {PluginCreator} from 'postcss'
import {PluginCreator, Rule, Root, Helpers} from 'postcss'

type Options = {
colors: Record<string, string | string[]>
Expand Down Expand Up @@ -27,9 +27,11 @@ const themeColors: PluginCreator<Options> = (options) => {
}
return colorKey in colors ? (colors[colorKey] as string) : colorKey
}
let processed = false
let injected: Rule[] = []
return {
postcssPlugin: 'postcss-theme-colors',
Declaration(decl) {
Declaration(decl, helper) {
const value = decl.value
if (!reRelativeColor.test(value) && !reMixColor.test(value)) {
return
Expand All @@ -52,10 +54,41 @@ const themeColors: PluginCreator<Options> = (options) => {
prop: decl.prop,
value: `var(${name})`,
})
processed = true
},
Once(doc, helper) {
injected = injectFlags(doc, helper)
},
OnceExit() {
if (!processed) {
injected.forEach((r) => r.remove())
}
injected = []
processed = false
},
}
}

function injectFlags(root: Root, helper: Helpers) {
const r2 = helper.rule({
selector: ':root[data-theme="dark"]',
})
r2.append(
helper.decl({prop: '--flag-light', value: ' '}),
helper.decl({prop: '--flag-dark', value: 'initial'})
)
root.prepend(r2)
const r1 = helper.rule({
selector: ':root',
})
r1.append(
helper.decl({prop: '--flag-light', value: 'initial'}),
helper.decl({prop: '--flag-dark', value: ' '})
)
root.prepend(r1)
return [r1, r2]
}

// djb2 hash function: http://www.cse.yorku.ca/~oz/hash.html
function hash(v: string) {
let hash = 5381
Expand Down
34 changes: 2 additions & 32 deletions test/__snapshots__/index.spec.ts.snap
Original file line number Diff line number Diff line change
@@ -1,35 +1,5 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`postcss-theme-colors > process in correct order 1`] = `
".element {
color: var(--G01);
}
exports[`use nesting plugin 1`] = `"a { color: var(--G01) }"`;

.element--modifier {
color: var(--G02);
}"
`;

exports[`postcss-theme-colors > process multiple value 1`] = `
"a {
box-shadow: 0 0 0 2px var(--G01), 0 0 0 4px var(--G02)
}"
`;

exports[`postcss-theme-colors > process nested rules 1`] = `
"a span {
color: var(--G01);
}"
`;

exports[`postcss-theme-colors > process single value 1`] = `
"a {
color: var(--G01);
}"
`;

exports[`postcss-theme-colors > process with custom root class of dark theme 1`] = `"a { color: var(--G01) }"`;

exports[`postcss-theme-colors > use nesting plugin 1`] = `"a { color: var(--G01) }"`;

exports[`postcss-theme-colors > use nesting plugin 2`] = `"a { color: var(--G01) }"`;
exports[`use nesting plugin 2`] = `"a { color: var(--G01) }"`;
222 changes: 98 additions & 124 deletions test/index.spec.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,16 @@
import postcss from 'postcss'
import type {Plugin} from 'postcss'
import {describe, it, expect} from 'vitest'
// import presetEnv from 'postcss-preset-env'
import {test, expect} from 'vitest'
import presetEnv from 'postcss-preset-env'
import themeColors from '../src'

const groups = {
'--G01': ['--C01', '--C02'],
'--G02': ['--C03', '--C04'],
}

const colors = {
'--C01': '#eee',
'--C02': '#111',
'--C03': '#f00',
'--C04': '#0f0',
...groups,
'--G01': ['--C01', '--C02'],
'--G02': ['--C03', '--C04'],
}

type ExtraOpts = {
Expand All @@ -23,159 +19,137 @@ type ExtraOpts = {
}
const process = async (
css: string,
options?: Omit<Parameters<typeof themeColors>[0], 'colors' | 'groups'> | null,
options?: Omit<Parameters<typeof themeColors>[0], 'colors'> | null,
{nestPlugin = 'postcss-nested', plugins = []}: ExtraOpts = {}
) => {
return postcss([
themeColors(Object.assign({colors, groups}, options)),
themeColors({colors, ...options}),
presetEnv(),
...(nestPlugin !== null ? [(await import(nestPlugin)).default] : []),
...plugins,
]).process(css, {
from: undefined,
})
}

describe('postcss-theme-colors', () => {
it('use with relative color syntax', async () => {
const input = `
test('use with relative color syntax', async () => {
const input = `
a {
color: oklch(from var(--G01) l c h / .8);
border: oklch(from var(--G01) .8 c h);
background: oklch(from var(--G01) calc(l * .8) c h);
}
`
const result = await process(input, null, {
plugins: [
(
await import('@csstools/postcss-relative-color-syntax')
).default({
// preserve: true,
}),
],
})
expect(result.css).toMatchInlineSnapshot(`
":root {
--flag-light: initial;
--flag-dark: ;
}
:root[data-theme="dark"] {
--flag-light: ;
--flag-dark: initial;
}
a {
--v1868641635: var(--flag-light, rgba(238, 238, 238, 0.8)) var(--flag-dark, rgba(17, 17, 17, 0.8));
color: var(--v1868641635);
--v1293428520: var(--flag-light, rgb(190, 190, 190)) var(--flag-dark, rgb(190, 190, 190));
border: var(--v1293428520);
--v3397449538: var(--flag-light, rgb(177, 177, 177)) var(--flag-dark, rgb(9, 9, 9));
background: var(--v3397449538);
}
@supports (color: lab(from red l 1 1% / calc(alpha + 0.1))) {
a {
color: oklch(from var(--G01) l c h / .8);
border: oklch(from var(--G01) .8 c h);
background: oklch(from var(--G01) calc(l * .8) c h);
}
}
"
`)
})
const result = await process(input, null, {})
expect(result.css).toMatchInlineSnapshot(`
":root {
--flag-light: initial;
--flag-dark: ;
}
:root[data-theme="dark"] {
--flag-light: ;
--flag-dark: initial;
}
a {
--v1868641635: var(--flag-light, rgba(238, 238, 238, 0.8)) var(--flag-dark, rgba(17, 17, 17, 0.8));
color: rgba(238, 238, 238, 0.8) ;
color: var(--v1868641635);
--v1293428520: var(--flag-light, rgb(190, 190, 190)) var(--flag-dark, rgb(190, 190, 190));
border: var(--v1293428520);
--v3397449538: var(--flag-light, rgb(177, 177, 177)) var(--flag-dark, rgb(9, 9, 9));
background: var(--v3397449538);
}
@supports (color: lab(from red l 1 1% / calc(alpha + 0.1))) {
a {
color: oklch(from var(--G01) l c h / .8);
border: oklch(from var(--G01) .8 c h);
background: oklch(from var(--G01) calc(l * .8) c h);
}
}
"
`)
})

it('use with color-mix()', async () => {
const input = `
test('use with color-mix()', async () => {
const input = `
a {
color: color-mix(in srgb, var(--G01), transparent 20%);
}
`
const result = await process(input, null, {
plugins: [require('@csstools/postcss-color-mix-function')],
})
expect(result.css).toMatchInlineSnapshot(`
"
a {
color: color-mix(in srgb, var(--G01), transparent 20%);
}
"
`)
})
const result = await process(input, null, {})
expect(result.css).toMatchInlineSnapshot(`
":root {
--flag-light: initial;
--flag-dark: ;
}
:root[data-theme="dark"] {
--flag-light: ;
--flag-dark: initial;
}
a {
--v546761730: var(--flag-light, rgba(238, 238, 238, 0.8)) var(--flag-dark, rgba(17, 17, 17, 0.8));
color: rgba(238, 238, 238, 0.8) ;
color: var(--v546761730);
}
@supports (color: color-mix(in lch, red, blue)) {
a {
color: color-mix(in srgb, var(--G01), transparent 20%);
}
}
"
`)
})

it('process single value', async () => {
const input = `a {
test('process single value', async () => {
const input = `a {
color: var(--G01);
}`
expect((await process(input)).css).toMatchSnapshot()
})
expect((await process(input)).css).toBe(input)
})

it('should not process invalid function name', async () => {
const input = `a {
test('should not process invalid function name', async () => {
const input = `a {
color: nonvar(--G01);
}`
expect((await process(input)).css).toBe(input)
})
expect((await process(input)).css).toBe(input)
})

it('process multiple value', async () => {
const input = `a {
test('process multiple value', async () => {
const input = `a {
box-shadow: 0 0 0 2px var(--G01), 0 0 0 4px var(--G02)
}`
expect((await process(input)).css).toMatchSnapshot()
})
expect((await process(input)).css).toMatchInlineSnapshot(`
"a {
box-shadow: 0 0 0 2px var(--G01), 0 0 0 4px var(--G02)
}"
`)
})

it('process nested rules', async () => {
const input = `a {
test('process nested rules', async () => {
const input = `a {
span {
color: var(--G01);
}
}`
expect((await process(input)).css).toMatchSnapshot()
})

it('process in correct order', async () => {
const input = `.element {
color: var(--G01);
&--modifier {
color: var(--G02);
}
}`
expect((await process(input)).css).toMatchSnapshot()
})
expect((await process(input)).css).toMatchInlineSnapshot(`
"a span {
color: var(--G01);
}"
`)
})

it('process undefined group', async () => {
const input = `a {
test('process undefined group', async () => {
const input = `a {
color: var(--GXX);
}`
const result = await process(input)
expect(result.css).toBe(input)
expect(result.messages).toMatchObject([
// https://github.com/csstools/postcss-plugins/discussions/192
{type: 'warning', text: 'Group not found: `var(--GXX)`'},
])
})

it('process with custom root class of dark theme', async () => {
expect(
(
await process(
`a { color: var(--G01) }`,
{darkThemeSelector: '.theme-dark'},
{nestPlugin: 'postcss-nesting'}
)
).css
).toMatchSnapshot()
})

it('process without nest plugin', async () => {
const noNestPluginProcess = (css: string) =>
postcss([themeColors({colors})]).process(css)
const result = await noNestPluginProcess(`a { color: var(--G01) }`)
expect(result.css).toMatchInlineSnapshot(`"a { color: var(--G01) }"`)
})
const result = await process(input)
expect(result.css).toBe(input)
})

it.each([
['postcss-nested', `a { color: var(--G01) }`],
['postcss-nesting', `a { color: var(--G01) }`],
])('use nesting plugin', async (nestPlugin, input) => {
const result = await process(input, null, {nestPlugin})
expect(result.css).toMatchSnapshot()
})
test.each([
['postcss-nested', `a { color: var(--G01) }`],
['postcss-nesting', `a { color: var(--G01) }`],
])('use nesting plugin', async (nestPlugin, input) => {
const result = await process(input, null, {nestPlugin})
expect(result.css).toMatchSnapshot()
})

0 comments on commit 9a71a18

Please sign in to comment.