Skip to content

Commit 916f9bf

Browse files
committed
test: add custom spacing tests for spacing, sizing, and transforms
Add comprehensive test coverage for custom spacing support across parsers: - Spacing utilities (m-*, p-*, gap-*) - Sizing utilities (w-*, h-*, min-*, max-*) - Transform utilities (translate-x-*, translate-y-*) Tests cover custom values, overriding defaults, and merging behavior.
1 parent 79c29a2 commit 916f9bf

File tree

6 files changed

+212
-28
lines changed

6 files changed

+212
-28
lines changed

src/parser/sizing.test.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,3 +254,59 @@ describe("parseSizing - comprehensive coverage", () => {
254254
expect(parseSizing("h-full")).toEqual({ height: "100%" });
255255
});
256256
});
257+
258+
describe("parseSizing - custom spacing", () => {
259+
const customSpacing = {
260+
xs: 4,
261+
sm: 8,
262+
md: 16,
263+
lg: 32,
264+
xl: 64,
265+
"4": 20, // Override default (16)
266+
};
267+
268+
it("should support custom spacing values for width", () => {
269+
expect(parseSizing("w-xs", customSpacing)).toEqual({ width: 4 });
270+
expect(parseSizing("w-sm", customSpacing)).toEqual({ width: 8 });
271+
expect(parseSizing("w-lg", customSpacing)).toEqual({ width: 32 });
272+
expect(parseSizing("w-xl", customSpacing)).toEqual({ width: 64 });
273+
});
274+
275+
it("should support custom spacing values for height", () => {
276+
expect(parseSizing("h-xs", customSpacing)).toEqual({ height: 4 });
277+
expect(parseSizing("h-md", customSpacing)).toEqual({ height: 16 });
278+
expect(parseSizing("h-xl", customSpacing)).toEqual({ height: 64 });
279+
});
280+
281+
it("should support custom spacing values for min/max dimensions", () => {
282+
expect(parseSizing("min-w-sm", customSpacing)).toEqual({ minWidth: 8 });
283+
expect(parseSizing("min-h-lg", customSpacing)).toEqual({ minHeight: 32 });
284+
expect(parseSizing("max-w-xl", customSpacing)).toEqual({ maxWidth: 64 });
285+
expect(parseSizing("max-h-md", customSpacing)).toEqual({ maxHeight: 16 });
286+
});
287+
288+
it("should allow custom spacing to override preset values", () => {
289+
expect(parseSizing("w-4", customSpacing)).toEqual({ width: 20 }); // Custom 20, not default 16
290+
expect(parseSizing("h-4", customSpacing)).toEqual({ height: 20 }); // Custom 20, not default 16
291+
});
292+
293+
it("should prefer arbitrary values over custom spacing", () => {
294+
expect(parseSizing("w-[24px]", customSpacing)).toEqual({ width: 24 }); // Arbitrary wins
295+
expect(parseSizing("h-[50]", customSpacing)).toEqual({ height: 50 }); // Arbitrary wins
296+
});
297+
298+
it("should fall back to preset scale for unknown custom keys", () => {
299+
expect(parseSizing("w-8", customSpacing)).toEqual({ width: 32 }); // Not overridden, uses preset
300+
expect(parseSizing("h-12", customSpacing)).toEqual({ height: 48 }); // Not overridden, uses preset
301+
});
302+
303+
it("should preserve percentage values with custom spacing", () => {
304+
expect(parseSizing("w-full", customSpacing)).toEqual({ width: "100%" });
305+
expect(parseSizing("h-1/2", customSpacing)).toEqual({ height: "50%" });
306+
});
307+
308+
it("should work without custom spacing (backward compatible)", () => {
309+
expect(parseSizing("w-4")).toEqual({ width: 16 }); // Default behavior
310+
expect(parseSizing("h-8")).toEqual({ height: 32 }); // Default behavior
311+
});
312+
});

src/parser/sizing.ts

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,13 @@ function parseArbitrarySize(value: string): number | string | null {
9595

9696
/**
9797
* Parse sizing classes
98+
* @param cls - The class name to parse
99+
* @param customSpacing - Optional custom spacing values from tailwind.config (shared with spacing utilities)
98100
*/
99-
export function parseSizing(cls: string): StyleObject | null {
101+
export function parseSizing(cls: string, customSpacing?: Record<string, number>): StyleObject | null {
102+
// Merge custom spacing with defaults (custom takes precedence)
103+
const sizeMap = customSpacing ? { ...SIZE_SCALE, ...customSpacing } : SIZE_SCALE;
104+
100105
// Width
101106
if (cls.startsWith("w-")) {
102107
const sizeKey = cls.substring(2);
@@ -106,7 +111,7 @@ export function parseSizing(cls: string): StyleObject | null {
106111
return { width: `${RUNTIME_DIMENSIONS_MARKER}width}}` } as StyleObject;
107112
}
108113

109-
// Arbitrary values: w-[123px], w-[50%]
114+
// Arbitrary values: w-[123px], w-[50%] (highest priority)
110115
const arbitrarySize = parseArbitrarySize(sizeKey);
111116
if (arbitrarySize !== null) {
112117
return { width: arbitrarySize };
@@ -118,8 +123,8 @@ export function parseSizing(cls: string): StyleObject | null {
118123
return { width: percentage };
119124
}
120125

121-
// Numeric widths: w-4, w-8, etc.
122-
const numericSize = SIZE_SCALE[sizeKey];
126+
// Numeric widths: w-4, w-8, etc. (includes custom spacing)
127+
const numericSize = sizeMap[sizeKey];
123128
if (numericSize !== undefined) {
124129
return { width: numericSize };
125130
}
@@ -139,7 +144,7 @@ export function parseSizing(cls: string): StyleObject | null {
139144
return { height: `${RUNTIME_DIMENSIONS_MARKER}height}}` } as StyleObject;
140145
}
141146

142-
// Arbitrary values: h-[123px], h-[50%]
147+
// Arbitrary values: h-[123px], h-[50%] (highest priority)
143148
const arbitrarySize = parseArbitrarySize(sizeKey);
144149
if (arbitrarySize !== null) {
145150
return { height: arbitrarySize };
@@ -151,8 +156,8 @@ export function parseSizing(cls: string): StyleObject | null {
151156
return { height: percentage };
152157
}
153158

154-
// Numeric heights: h-4, h-8, etc.
155-
const numericSize = SIZE_SCALE[sizeKey];
159+
// Numeric heights: h-4, h-8, etc. (includes custom spacing)
160+
const numericSize = sizeMap[sizeKey];
156161
if (numericSize !== undefined) {
157162
return { height: numericSize };
158163
}
@@ -167,7 +172,7 @@ export function parseSizing(cls: string): StyleObject | null {
167172
if (cls.startsWith("min-w-")) {
168173
const sizeKey = cls.substring(6);
169174

170-
// Arbitrary values: min-w-[123px], min-w-[50%]
175+
// Arbitrary values: min-w-[123px], min-w-[50%] (highest priority)
171176
const arbitrarySize = parseArbitrarySize(sizeKey);
172177
if (arbitrarySize !== null) {
173178
return { minWidth: arbitrarySize };
@@ -178,7 +183,7 @@ export function parseSizing(cls: string): StyleObject | null {
178183
return { minWidth: percentage };
179184
}
180185

181-
const numericSize = SIZE_SCALE[sizeKey];
186+
const numericSize = sizeMap[sizeKey];
182187
if (numericSize !== undefined) {
183188
return { minWidth: numericSize };
184189
}
@@ -188,7 +193,7 @@ export function parseSizing(cls: string): StyleObject | null {
188193
if (cls.startsWith("min-h-")) {
189194
const sizeKey = cls.substring(6);
190195

191-
// Arbitrary values: min-h-[123px], min-h-[50%]
196+
// Arbitrary values: min-h-[123px], min-h-[50%] (highest priority)
192197
const arbitrarySize = parseArbitrarySize(sizeKey);
193198
if (arbitrarySize !== null) {
194199
return { minHeight: arbitrarySize };
@@ -199,7 +204,7 @@ export function parseSizing(cls: string): StyleObject | null {
199204
return { minHeight: percentage };
200205
}
201206

202-
const numericSize = SIZE_SCALE[sizeKey];
207+
const numericSize = sizeMap[sizeKey];
203208
if (numericSize !== undefined) {
204209
return { minHeight: numericSize };
205210
}
@@ -209,7 +214,7 @@ export function parseSizing(cls: string): StyleObject | null {
209214
if (cls.startsWith("max-w-")) {
210215
const sizeKey = cls.substring(6);
211216

212-
// Arbitrary values: max-w-[123px], max-w-[50%]
217+
// Arbitrary values: max-w-[123px], max-w-[50%] (highest priority)
213218
const arbitrarySize = parseArbitrarySize(sizeKey);
214219
if (arbitrarySize !== null) {
215220
return { maxWidth: arbitrarySize };
@@ -220,7 +225,7 @@ export function parseSizing(cls: string): StyleObject | null {
220225
return { maxWidth: percentage };
221226
}
222227

223-
const numericSize = SIZE_SCALE[sizeKey];
228+
const numericSize = sizeMap[sizeKey];
224229
if (numericSize !== undefined) {
225230
return { maxWidth: numericSize };
226231
}
@@ -230,7 +235,7 @@ export function parseSizing(cls: string): StyleObject | null {
230235
if (cls.startsWith("max-h-")) {
231236
const sizeKey = cls.substring(6);
232237

233-
// Arbitrary values: max-h-[123px], max-h-[50%]
238+
// Arbitrary values: max-h-[123px], max-h-[50%] (highest priority)
234239
const arbitrarySize = parseArbitrarySize(sizeKey);
235240
if (arbitrarySize !== null) {
236241
return { maxHeight: arbitrarySize };
@@ -241,7 +246,7 @@ export function parseSizing(cls: string): StyleObject | null {
241246
return { maxHeight: percentage };
242247
}
243248

244-
const numericSize = SIZE_SCALE[sizeKey];
249+
const numericSize = sizeMap[sizeKey];
245250
if (numericSize !== undefined) {
246251
return { maxHeight: numericSize };
247252
}

src/parser/spacing.test.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -415,3 +415,60 @@ describe("parseSpacing - logical padding (RTL-aware)", () => {
415415
expect(parseSpacing("pe-[24]")).toEqual({ paddingEnd: 24 });
416416
});
417417
});
418+
419+
describe("parseSpacing - custom spacing", () => {
420+
const customSpacing = {
421+
xs: 4,
422+
sm: 8,
423+
md: 16,
424+
lg: 32,
425+
xl: 64,
426+
"4": 20, // Override default (16)
427+
};
428+
429+
it("should support custom spacing values for margin", () => {
430+
expect(parseSpacing("m-xs", customSpacing)).toEqual({ margin: 4 });
431+
expect(parseSpacing("m-sm", customSpacing)).toEqual({ margin: 8 });
432+
expect(parseSpacing("m-lg", customSpacing)).toEqual({ margin: 32 });
433+
expect(parseSpacing("mx-xl", customSpacing)).toEqual({ marginHorizontal: 64 });
434+
expect(parseSpacing("mt-md", customSpacing)).toEqual({ marginTop: 16 });
435+
});
436+
437+
it("should support custom spacing values for padding", () => {
438+
expect(parseSpacing("p-xs", customSpacing)).toEqual({ padding: 4 });
439+
expect(parseSpacing("p-sm", customSpacing)).toEqual({ padding: 8 });
440+
expect(parseSpacing("px-lg", customSpacing)).toEqual({ paddingHorizontal: 32 });
441+
expect(parseSpacing("pt-xl", customSpacing)).toEqual({ paddingTop: 64 });
442+
});
443+
444+
it("should support custom spacing values for gap", () => {
445+
expect(parseSpacing("gap-xs", customSpacing)).toEqual({ gap: 4 });
446+
expect(parseSpacing("gap-md", customSpacing)).toEqual({ gap: 16 });
447+
expect(parseSpacing("gap-xl", customSpacing)).toEqual({ gap: 64 });
448+
});
449+
450+
it("should allow custom spacing to override preset values", () => {
451+
expect(parseSpacing("m-4", customSpacing)).toEqual({ margin: 20 }); // Custom 20, not default 16
452+
});
453+
454+
it("should prefer arbitrary values over custom spacing", () => {
455+
expect(parseSpacing("m-[24px]", customSpacing)).toEqual({ margin: 24 }); // Arbitrary wins
456+
expect(parseSpacing("p-[50]", customSpacing)).toEqual({ padding: 50 }); // Arbitrary wins
457+
});
458+
459+
it("should support negative margins with custom spacing", () => {
460+
expect(parseSpacing("-m-xs", customSpacing)).toEqual({ margin: -4 });
461+
expect(parseSpacing("-m-lg", customSpacing)).toEqual({ margin: -32 });
462+
expect(parseSpacing("-mx-xl", customSpacing)).toEqual({ marginHorizontal: -64 });
463+
});
464+
465+
it("should fall back to preset scale for unknown custom keys", () => {
466+
expect(parseSpacing("m-8", customSpacing)).toEqual({ margin: 32 }); // Not overridden, uses preset
467+
expect(parseSpacing("p-12", customSpacing)).toEqual({ padding: 48 }); // Not overridden, uses preset
468+
});
469+
470+
it("should work without custom spacing (backward compatible)", () => {
471+
expect(parseSpacing("m-4")).toEqual({ margin: 16 }); // Default behavior
472+
expect(parseSpacing("p-8")).toEqual({ padding: 32 }); // Default behavior
473+
});
474+
});

src/parser/spacing.ts

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,13 @@ function parseArbitrarySpacing(value: string): number | null {
7070
/**
7171
* Parse spacing classes (margin, padding, gap)
7272
* Examples: m-4, mx-2, mt-8, p-4, px-2, pt-8, gap-4, m-[16px], pl-[4.5px], -m-4, -mt-[10px], ms-4, pe-2
73+
* @param cls - The class name to parse
74+
* @param customSpacing - Optional custom spacing values from tailwind.config
7375
*/
74-
export function parseSpacing(cls: string): StyleObject | null {
76+
export function parseSpacing(cls: string, customSpacing?: Record<string, number>): StyleObject | null {
77+
// Merge custom spacing with defaults (custom takes precedence)
78+
const spacingMap = customSpacing ? { ...SPACING_SCALE, ...customSpacing } : SPACING_SCALE;
79+
7580
// Margin: m-4, mx-2, mt-8, ms-4, me-2, m-[16px], -m-4, -mt-2, etc.
7681
// Supports negative values for margins (but not padding or gap)
7782
// s = start (RTL-aware), e = end (RTL-aware)
@@ -80,15 +85,15 @@ export function parseSpacing(cls: string): StyleObject | null {
8085
const [, negativePrefix, dir, valueStr] = marginMatch;
8186
const isNegative = negativePrefix === "-";
8287

83-
// Try arbitrary value first
88+
// Try arbitrary value first (highest priority)
8489
const arbitraryValue = parseArbitrarySpacing(valueStr);
8590
if (arbitraryValue !== null) {
8691
const finalValue = isNegative ? -arbitraryValue : arbitraryValue;
8792
return getMarginStyle(dir, finalValue);
8893
}
8994

90-
// Try preset scale
91-
const scaleValue = SPACING_SCALE[valueStr];
95+
// Try spacing scale (includes custom spacing)
96+
const scaleValue = spacingMap[valueStr];
9297
if (scaleValue !== undefined) {
9398
const finalValue = isNegative ? -scaleValue : scaleValue;
9499
return getMarginStyle(dir, finalValue);
@@ -101,14 +106,14 @@ export function parseSpacing(cls: string): StyleObject | null {
101106
if (paddingMatch) {
102107
const [, dir, valueStr] = paddingMatch;
103108

104-
// Try arbitrary value first
109+
// Try arbitrary value first (highest priority)
105110
const arbitraryValue = parseArbitrarySpacing(valueStr);
106111
if (arbitraryValue !== null) {
107112
return getPaddingStyle(dir, arbitraryValue);
108113
}
109114

110-
// Try preset scale
111-
const scaleValue = SPACING_SCALE[valueStr];
115+
// Try spacing scale (includes custom spacing)
116+
const scaleValue = spacingMap[valueStr];
112117
if (scaleValue !== undefined) {
113118
return getPaddingStyle(dir, scaleValue);
114119
}
@@ -119,14 +124,14 @@ export function parseSpacing(cls: string): StyleObject | null {
119124
if (gapMatch) {
120125
const valueStr = gapMatch[1];
121126

122-
// Try arbitrary value first
127+
// Try arbitrary value first (highest priority)
123128
const arbitraryValue = parseArbitrarySpacing(valueStr);
124129
if (arbitraryValue !== null) {
125130
return { gap: arbitraryValue };
126131
}
127132

128-
// Try preset scale
129-
const scaleValue = SPACING_SCALE[valueStr];
133+
// Try spacing scale (includes custom spacing)
134+
const scaleValue = spacingMap[valueStr];
130135
if (scaleValue !== undefined) {
131136
return { gap: scaleValue };
132137
}

src/parser/transforms.test.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,3 +316,60 @@ describe("parseTransform - case sensitivity", () => {
316316
expect(parseTransform("ROTATE-45")).toBeNull();
317317
});
318318
});
319+
320+
describe("parseTransform - custom spacing", () => {
321+
const customSpacing = {
322+
xs: 4,
323+
sm: 8,
324+
md: 16,
325+
lg: 32,
326+
xl: 64,
327+
"4": 20, // Override default (16)
328+
};
329+
330+
it("should support custom spacing values for translateX", () => {
331+
expect(parseTransform("translate-x-xs", customSpacing)).toEqual({ transform: [{ translateX: 4 }] });
332+
expect(parseTransform("translate-x-sm", customSpacing)).toEqual({ transform: [{ translateX: 8 }] });
333+
expect(parseTransform("translate-x-lg", customSpacing)).toEqual({ transform: [{ translateX: 32 }] });
334+
expect(parseTransform("translate-x-xl", customSpacing)).toEqual({ transform: [{ translateX: 64 }] });
335+
});
336+
337+
it("should support custom spacing values for translateY", () => {
338+
expect(parseTransform("translate-y-xs", customSpacing)).toEqual({ transform: [{ translateY: 4 }] });
339+
expect(parseTransform("translate-y-md", customSpacing)).toEqual({ transform: [{ translateY: 16 }] });
340+
expect(parseTransform("translate-y-xl", customSpacing)).toEqual({ transform: [{ translateY: 64 }] });
341+
});
342+
343+
it("should support negative custom spacing for translate", () => {
344+
expect(parseTransform("-translate-x-sm", customSpacing)).toEqual({ transform: [{ translateX: -8 }] });
345+
expect(parseTransform("-translate-y-lg", customSpacing)).toEqual({ transform: [{ translateY: -32 }] });
346+
});
347+
348+
it("should allow custom spacing to override preset values", () => {
349+
expect(parseTransform("translate-x-4", customSpacing)).toEqual({ transform: [{ translateX: 20 }] }); // Custom 20, not default 16
350+
expect(parseTransform("translate-y-4", customSpacing)).toEqual({ transform: [{ translateY: 20 }] }); // Custom 20, not default 16
351+
});
352+
353+
it("should prefer arbitrary values over custom spacing", () => {
354+
expect(parseTransform("translate-x-[24px]", customSpacing)).toEqual({ transform: [{ translateX: 24 }] }); // Arbitrary wins
355+
expect(parseTransform("translate-y-[50]", customSpacing)).toEqual({ transform: [{ translateY: 50 }] }); // Arbitrary wins
356+
});
357+
358+
it("should fall back to preset scale for unknown custom keys", () => {
359+
expect(parseTransform("translate-x-8", customSpacing)).toEqual({ transform: [{ translateX: 32 }] }); // Not overridden, uses preset
360+
expect(parseTransform("translate-y-12", customSpacing)).toEqual({ transform: [{ translateY: 48 }] }); // Not overridden, uses preset
361+
});
362+
363+
it("should work without custom spacing (backward compatible)", () => {
364+
expect(parseTransform("translate-x-4")).toEqual({ transform: [{ translateX: 16 }] }); // Default behavior
365+
expect(parseTransform("translate-y-8")).toEqual({ transform: [{ translateY: 32 }] }); // Default behavior
366+
});
367+
368+
it("should not affect non-translate transforms", () => {
369+
// Scale, rotate, skew, perspective should not use custom spacing
370+
expect(parseTransform("scale-110", customSpacing)).toEqual({ transform: [{ scale: 1.1 }] });
371+
expect(parseTransform("rotate-45", customSpacing)).toEqual({ transform: [{ rotate: "45deg" }] });
372+
expect(parseTransform("skew-x-6", customSpacing)).toEqual({ transform: [{ skewX: "6deg" }] });
373+
expect(parseTransform("perspective-500", customSpacing)).toEqual({ transform: [{ perspective: 500 }] });
374+
});
375+
});

0 commit comments

Comments
 (0)