Skip to content

Commit dff48dd

Browse files
committed
feat: introduce legacy option, add complete tests
* Add legacy option to plugin initialisation * Create complete tests of compiled CSS (100% coverage) * Some refactoring of classes, including extending from a Base class
1 parent 40e2074 commit dff48dd

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+3114
-466
lines changed

jest.config.js

+1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ const jestConfig = {
3434
moduleNameMapper: {
3535
'^@/(.*)$': '<rootDir>/src/$1',
3636
},
37+
coveragePathIgnorePatterns: ['<rootDir>/jest'],
3738
}
3839

3940
module.exports = jestConfig

jest/utils/compile-css.ts

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import postcss from 'postcss'
2+
import tailwindcss from 'tailwindcss'
3+
import { resolve } from 'node:path'
4+
import tailwindcss3d from '../../src'
5+
import { type Config } from 'tailwindcss/types/config'
6+
7+
type Theme = Config['theme']
8+
9+
export const compileCSS = async (
10+
html: string,
11+
legacy = false,
12+
theme?: Theme
13+
) => {
14+
const { currentTestName } = expect.getState()
15+
16+
return postcss(
17+
tailwindcss({
18+
content: [
19+
{
20+
raw: html,
21+
},
22+
],
23+
plugins: [tailwindcss3d({ legacy })],
24+
theme,
25+
})
26+
).process('@tailwind utilities;', {
27+
from: `${resolve(__filename)}?test=${currentTestName}`,
28+
}).css
29+
}

jest/utils/css-reg-exp.ts

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
const escape = (value: string): string =>
2+
value.replace(/[/\-\\^$*+?.()|[\]{}]/g, '\\$&')
3+
4+
export const cssClassRegExp = (
5+
className: string,
6+
properties: string | string[]
7+
): RegExp => {
8+
const escapedProperties = [properties].flat().map(escape).join(';\\s+')
9+
return new RegExp(
10+
`.*\\.${escape(className)}\\s+\\{\\s+${escapedProperties}\\s+\\}.*`
11+
)
12+
}
13+
14+
const cssKeyframeRegExpString = (
15+
marker: string,
16+
properties: string | string[]
17+
): string => {
18+
const escapedProperties = [properties].flat().map(escape).join(';\\s+')
19+
return `.*${escape(marker)}\\s+\\{\\s+${escapedProperties}\\s+\\}.*`
20+
}
21+
22+
export const cssKeyframesRegExp = (
23+
name: string,
24+
keyframes: Array<[string, string | string[]]>
25+
): RegExp => {
26+
const escapedkeyframes = [keyframes]
27+
.flat()
28+
.map(([marker, properties]) => cssKeyframeRegExpString(marker, properties))
29+
.join('\\s+')
30+
return new RegExp(
31+
`.*@keyframes\\s+${escape(name)}\\s+\\{\\s+${escapedkeyframes}\\s+\\}.*`
32+
)
33+
}

jest/utils/index.ts

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export * from './compile-css'
2+
export * from './css-reg-exp'
3+
export * from './legacy-transform'

jest/utils/legacy-transform.ts

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
type List = Record<string, string>
2+
3+
const replace = (list: List, replacements?: List): List => {
4+
if (
5+
typeof replacements !== 'object' ||
6+
replacements === null ||
7+
Array.isArray(replacements)
8+
) {
9+
return list
10+
}
11+
12+
const mappedListEntries: [string, string][] = Object.entries(list).map(
13+
([key, value]) => [key, replacements[key] ?? value]
14+
)
15+
return Object.fromEntries(mappedListEntries)
16+
}
17+
18+
const replaceValues = (list: List, replacements?: List): string =>
19+
Object.entries(replace(list, replacements))
20+
.map(([key, value]) => `${key}(${value})`)
21+
.join(' ')
22+
23+
export const legacyTransform2DValue = (replacements?: List) =>
24+
replaceValues(
25+
{
26+
translate: 'var(--tw-translate-x), var(--tw-translate-y)',
27+
rotate: 'var(--tw-rotate-z)',
28+
skewX: 'var(--tw-skew-x)',
29+
skewY: 'var(--tw-skew-y)',
30+
scaleX: 'var(--tw-scale-x)',
31+
scaleY: 'var(--tw-scale-y)',
32+
},
33+
replacements
34+
)
35+
36+
export const legacyTransform3DValue = (replacements?: List): string =>
37+
replaceValues(
38+
{
39+
translate3d:
40+
'var(--tw-translate-x), var(--tw-translate-y), var(--tw-translate-z)',
41+
rotateX: 'var(--tw-rotate-x)',
42+
rotateY: 'var(--tw-rotate-y)',
43+
rotateZ: 'var(--tw-rotate-z)',
44+
skewX: 'var(--tw-skew-x)',
45+
skewY: 'var(--tw-skew-y)',
46+
scaleX: 'var(--tw-scale-x)',
47+
scaleY: 'var(--tw-scale-y)',
48+
scaleZ: 'var(--tw-scale-z)',
49+
perspective: 'var(--tw-perspective)',
50+
},
51+
replacements
52+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
import {
2+
compileCSS,
3+
cssClassRegExp,
4+
cssKeyframesRegExp,
5+
// legacyTransform3DValue,
6+
} from '../../../../jest/utils'
7+
8+
const testClassNames = [
9+
'animate-bounce-and-spin-x',
10+
'animate-bounce-and-spin-y-2',
11+
'-animate-bounce-and-spin-z-4',
12+
]
13+
const testElements = testClassNames
14+
.map((className) => `<div class="${className}"></div>`)
15+
.join('')
16+
17+
describe('css-animations', () => {
18+
describe('BounceAndSpin', () => {
19+
it('compiles', async () => {
20+
const css = await compileCSS(testElements)
21+
22+
expect(css).toMatch(
23+
cssKeyframesRegExp('bounce-and-spin-x', [
24+
[
25+
'0%, 100%',
26+
[
27+
'translate: 0px var(--tw-translate-y) var(--tw-translate-z)',
28+
'animation-timing-function: cubic-bezier(0, 0, 0.2, 1)',
29+
],
30+
],
31+
[
32+
'0%, 5%',
33+
[
34+
'--webkit-transform: rotateX(0deg) rotateY(var(--tw-rotate-y)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))',
35+
'transform: rotateX(0deg) rotateY(var(--tw-rotate-y)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))',
36+
'animation-timing-function: ease-in',
37+
],
38+
],
39+
[
40+
'50%',
41+
[
42+
'translate: 0.25rem var(--tw-translate-y) var(--tw-translate-z)',
43+
'animation-timing-function: cubic-bezier(0.8, 0, 1, 1)',
44+
],
45+
],
46+
[
47+
'50.1%',
48+
[
49+
'--webkit-transform: rotateX(180deg) rotateY(var(--tw-rotate-y)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))',
50+
'transform: rotateX(180deg) rotateY(var(--tw-rotate-y)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))',
51+
'animation-timing-function: linear',
52+
],
53+
],
54+
[
55+
'95%, 100%',
56+
[
57+
'--webkit-transform: rotateX(360deg) rotateY(var(--tw-rotate-y)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))',
58+
'transform: rotateX(360deg) rotateY(var(--tw-rotate-y)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))',
59+
'animation-timing-function: ease-out',
60+
],
61+
],
62+
])
63+
)
64+
expect(css).toMatch(
65+
cssClassRegExp(
66+
'animate-bounce-and-spin-x',
67+
'animation: bounce-and-spin-x 1s infinite'
68+
)
69+
)
70+
71+
expect(css).toMatch(
72+
cssKeyframesRegExp('bounce-and-spin-y-2', [
73+
[
74+
'0%, 100%',
75+
[
76+
'translate: var(--tw-translate-x) 0px var(--tw-translate-z)',
77+
'animation-timing-function: cubic-bezier(0, 0, 0.2, 1)',
78+
],
79+
],
80+
[
81+
'0%, 5%',
82+
[
83+
'--webkit-transform: rotateX(var(--tw-rotate-x)) rotateY(0deg) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))',
84+
'transform: rotateX(var(--tw-rotate-x)) rotateY(0deg) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))',
85+
'animation-timing-function: ease-in',
86+
],
87+
],
88+
[
89+
'50%',
90+
[
91+
'translate: var(--tw-translate-x) 0.5rem var(--tw-translate-z)',
92+
'animation-timing-function: cubic-bezier(0.8, 0, 1, 1)',
93+
],
94+
],
95+
[
96+
'50.1%',
97+
[
98+
'--webkit-transform: rotateX(var(--tw-rotate-x)) rotateY(180deg) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))',
99+
'transform: rotateX(var(--tw-rotate-x)) rotateY(180deg) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))',
100+
'animation-timing-function: linear',
101+
],
102+
],
103+
[
104+
'95%, 100%',
105+
[
106+
'--webkit-transform: rotateX(var(--tw-rotate-x)) rotateY(360deg) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))',
107+
'transform: rotateX(var(--tw-rotate-x)) rotateY(360deg) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y))',
108+
'animation-timing-function: ease-out',
109+
],
110+
],
111+
])
112+
)
113+
expect(css).toMatch(
114+
cssClassRegExp(
115+
'animate-bounce-and-spin-y-2',
116+
'animation: bounce-and-spin-y-2 1.19s infinite'
117+
)
118+
)
119+
120+
expect(css).toMatch(
121+
cssKeyframesRegExp('-bounce-and-spin-z-4', [
122+
[
123+
'0%, 100%',
124+
[
125+
'translate: var(--tw-translate-x) var(--tw-translate-y) 0px',
126+
'animation-timing-function: cubic-bezier(0, 0, 0.2, 1)',
127+
],
128+
],
129+
['0%, 5%', ['rotate: 0deg', 'animation-timing-function: ease-in']],
130+
[
131+
'50%',
132+
[
133+
'translate: var(--tw-translate-x) var(--tw-translate-y) -1rem',
134+
'animation-timing-function: cubic-bezier(0.8, 0, 1, 1)',
135+
],
136+
],
137+
['50.1%', ['rotate: -180deg', 'animation-timing-function: linear']],
138+
[
139+
'95%, 100%',
140+
['rotate: -360deg', 'animation-timing-function: ease-out'],
141+
],
142+
])
143+
)
144+
expect(css).toMatch(
145+
cssClassRegExp(
146+
'-animate-bounce-and-spin-z-4',
147+
'animation: -bounce-and-spin-z-4 1.41s infinite'
148+
)
149+
)
150+
})
151+
152+
// There is no "legacy" version of BounceAndSpin
153+
})
154+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { compileCSS } from '../../../../jest/utils'
2+
3+
describe('css-animations', () => {
4+
describe('BounceAndSpin', () => {
5+
describe('normaliseValues()', () => {
6+
it('will not produce CSS if no valid bounce values are present in the theme', async () => {
7+
const css = await compileCSS(
8+
'<div class="animate-bounce-and-spin-y"></div>',
9+
false,
10+
{ bounceAndSpin: { '0': 0 } }
11+
)
12+
13+
expect(css).toBe('')
14+
})
15+
})
16+
})
17+
})

0 commit comments

Comments
 (0)