Skip to content

Commit 36fc03b

Browse files
committed
Add initial support for applying variants and other complex classes
1 parent 1791768 commit 36fc03b

File tree

4 files changed

+563
-0
lines changed

4 files changed

+563
-0
lines changed
Lines changed: 348 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,348 @@
1+
import postcss from 'postcss'
2+
import substituteClassApplyAtRules from '../src/lib/substituteClassApplyAtRules'
3+
import processPlugins from '../src/util/processPlugins'
4+
import resolveConfig from '../src/util/resolveConfig'
5+
import corePlugins from '../src/corePlugins'
6+
import defaultConfig from '../stubs/defaultConfig.stub.js'
7+
8+
const resolvedDefaultConfig = resolveConfig([defaultConfig])
9+
10+
const { utilities: defaultUtilities } = processPlugins(
11+
corePlugins(resolvedDefaultConfig),
12+
resolvedDefaultConfig
13+
)
14+
15+
function run(input, config = resolvedDefaultConfig, utilities = defaultUtilities) {
16+
config.experimental = {
17+
applyComplexClasses: true,
18+
}
19+
return postcss([substituteClassApplyAtRules(config, utilities)]).process(input, {
20+
from: undefined,
21+
})
22+
}
23+
24+
test('it copies class declarations into itself', () => {
25+
const output = '.a { color: red; } .b { color: red; }'
26+
27+
return run('.a { color: red; } .b { @apply a; }').then(result => {
28+
expect(result.css).toEqual(output)
29+
expect(result.warnings().length).toBe(0)
30+
})
31+
})
32+
33+
test('selectors with invalid characters do not need to be manually escaped', () => {
34+
const input = `
35+
.a\\:1\\/2 { color: red; }
36+
.b { @apply a:1/2; }
37+
`
38+
39+
const expected = `
40+
.a\\:1\\/2 { color: red; }
41+
.b { color: red; }
42+
`
43+
44+
return run(input).then(result => {
45+
expect(result.css).toEqual(expected)
46+
expect(result.warnings().length).toBe(0)
47+
})
48+
})
49+
50+
test.skip('it removes important from applied classes by default', () => {
51+
const input = `
52+
.a { color: red !important; }
53+
.b { @apply a; }
54+
`
55+
56+
const expected = `
57+
.a { color: red !important; }
58+
.b { color: red; }
59+
`
60+
61+
return run(input).then(result => {
62+
expect(result.css).toEqual(expected)
63+
expect(result.warnings().length).toBe(0)
64+
})
65+
})
66+
67+
test.skip('applied rules can be made !important', () => {
68+
const input = `
69+
.a { color: red; }
70+
.b { @apply a !important; }
71+
`
72+
73+
const expected = `
74+
.a { color: red; }
75+
.b { color: red !important; }
76+
`
77+
78+
return run(input).then(result => {
79+
expect(result.css).toEqual(expected)
80+
expect(result.warnings().length).toBe(0)
81+
})
82+
})
83+
84+
test.skip('cssnext custom property sets are preserved', () => {
85+
const input = `
86+
.a {
87+
color: red;
88+
}
89+
.b {
90+
@apply a --custom-property-set;
91+
}
92+
`
93+
94+
const expected = `
95+
.a {
96+
color: red;
97+
}
98+
.b {
99+
color: red;
100+
@apply --custom-property-set;
101+
}
102+
`
103+
104+
return run(input).then(result => {
105+
expect(result.css).toEqual(expected)
106+
expect(result.warnings().length).toBe(0)
107+
})
108+
})
109+
110+
test('it fails if the class does not exist', () => {
111+
return run('.b { @apply a; }').catch(e => {
112+
expect(e).toMatchObject({ name: 'CssSyntaxError' })
113+
})
114+
})
115+
116+
test('applying classes that are defined in a media query is supported', () => {
117+
const input = `
118+
@media (min-width: 300px) {
119+
.a { color: blue; }
120+
}
121+
122+
.b {
123+
@apply a;
124+
}
125+
`
126+
127+
const output = `
128+
@media (min-width: 300px) {
129+
.a { color: blue; }
130+
}
131+
@media (min-width: 300px) {
132+
.b { color: blue; }
133+
}
134+
`
135+
136+
return run(input).then(result => {
137+
expect(result.css).toMatchCss(output)
138+
expect(result.warnings().length).toBe(0)
139+
})
140+
})
141+
142+
test('applying classes that are used in a media query is supported', () => {
143+
const input = `
144+
.a {
145+
color: red;
146+
}
147+
148+
@media (min-width: 300px) {
149+
.a { color: blue; }
150+
}
151+
152+
.b {
153+
@apply a;
154+
}
155+
`
156+
157+
const output = `
158+
.a {
159+
color: red;
160+
}
161+
162+
@media (min-width: 300px) {
163+
.a { color: blue; }
164+
}
165+
166+
.b {
167+
color: red;
168+
}
169+
170+
@media (min-width: 300px) {
171+
.b { color: blue; }
172+
}
173+
`
174+
175+
return run(input).then(result => {
176+
expect(result.css).toMatchCss(output)
177+
expect(result.warnings().length).toBe(0)
178+
})
179+
})
180+
181+
test('it matches classes that include pseudo-selectors', () => {
182+
const input = `
183+
.a:hover {
184+
color: red;
185+
}
186+
187+
.b {
188+
@apply a;
189+
}
190+
`
191+
192+
const output = `
193+
.a:hover {
194+
color: red;
195+
}
196+
197+
.b:hover {
198+
color: red;
199+
}
200+
`
201+
202+
return run(input).then(result => {
203+
expect(result.css).toMatchCss(output)
204+
expect(result.warnings().length).toBe(0)
205+
})
206+
})
207+
208+
test('it matches classes that have multiple rules', () => {
209+
const input = `
210+
.a {
211+
color: red;
212+
}
213+
214+
.b {
215+
@apply a;
216+
}
217+
218+
.a {
219+
color: blue;
220+
}
221+
`
222+
223+
const output = `
224+
.a {
225+
color: red;
226+
}
227+
228+
.b {
229+
color: red;
230+
color: blue;
231+
}
232+
233+
.a {
234+
color: blue;
235+
}
236+
`
237+
238+
return run(input).then(result => {
239+
expect(result.css).toMatchCss(output)
240+
expect(result.warnings().length).toBe(0)
241+
})
242+
})
243+
244+
test.skip('you can apply utility classes that do not actually exist as long as they would exist if utilities were being generated', () => {
245+
const input = `
246+
.foo { @apply mt-4; }
247+
`
248+
249+
const expected = `
250+
.foo { margin-top: 1rem; }
251+
`
252+
253+
return run(input).then(result => {
254+
expect(result.css).toEqual(expected)
255+
expect(result.warnings().length).toBe(0)
256+
})
257+
})
258+
259+
test.skip('you can apply utility classes without using the given prefix', () => {
260+
const input = `
261+
.foo { @apply .tw-mt-4 .mb-4; }
262+
`
263+
264+
const expected = `
265+
.foo { margin-top: 1rem; margin-bottom: 1rem; }
266+
`
267+
268+
const config = resolveConfig([
269+
{
270+
...defaultConfig,
271+
prefix: 'tw-',
272+
},
273+
])
274+
275+
return run(input, config, processPlugins(corePlugins(config), config).utilities).then(result => {
276+
expect(result.css).toEqual(expected)
277+
expect(result.warnings().length).toBe(0)
278+
})
279+
})
280+
281+
test.skip('you can apply utility classes without using the given prefix when using a function for the prefix', () => {
282+
const input = `
283+
.foo { @apply .tw-mt-4 .mb-4; }
284+
`
285+
286+
const expected = `
287+
.foo { margin-top: 1rem; margin-bottom: 1rem; }
288+
`
289+
290+
const config = resolveConfig([
291+
{
292+
...defaultConfig,
293+
prefix: () => {
294+
return 'tw-'
295+
},
296+
},
297+
])
298+
299+
return run(input, config, processPlugins(corePlugins(config), config).utilities).then(result => {
300+
expect(result.css).toEqual(expected)
301+
expect(result.warnings().length).toBe(0)
302+
})
303+
})
304+
305+
test.skip('you can apply utility classes without specificity prefix even if important (selector) is used', () => {
306+
const input = `
307+
.foo { @apply .mt-8 .mb-8; }
308+
`
309+
310+
const expected = `
311+
.foo { margin-top: 2rem; margin-bottom: 2rem; }
312+
`
313+
314+
const config = resolveConfig([
315+
{
316+
...defaultConfig,
317+
important: '#app',
318+
},
319+
])
320+
321+
return run(input, config, processPlugins(corePlugins(config), config).utilities).then(result => {
322+
expect(result.css).toEqual(expected)
323+
expect(result.warnings().length).toBe(0)
324+
})
325+
})
326+
327+
test.skip('you can apply utility classes without using the given prefix even if important (selector) is used', () => {
328+
const input = `
329+
.foo { @apply .tw-mt-4 .mb-4; }
330+
`
331+
332+
const expected = `
333+
.foo { margin-top: 1rem; margin-bottom: 1rem; }
334+
`
335+
336+
const config = resolveConfig([
337+
{
338+
...defaultConfig,
339+
prefix: 'tw-',
340+
important: '#app',
341+
},
342+
])
343+
344+
return run(input, config, processPlugins(corePlugins(config), config).utilities).then(result => {
345+
expect(result.css).toEqual(expected)
346+
expect(result.warnings().length).toBe(0)
347+
})
348+
})

src/featureFlags.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const featureFlags = {
88
'extendedSpacingScale',
99
'defaultLineHeights',
1010
'extendedFontSizeScale',
11+
'applyComplexClasses',
1112
],
1213
}
1314

0 commit comments

Comments
 (0)