@@ -47,7 +47,7 @@ describe('StyleProcessor', () => {
4747 } ) ;
4848
4949 test ( 'parses custom properties' , ( ) => {
50- const result = parser . process ( '$my-gap $( my-gap, 2x)' ) ;
50+ const result = parser . process ( '$my-gap ($ my-gap, 2x)' ) ;
5151 expect ( result . groups [ 0 ] . values ) . toEqual ( [
5252 'var(--my-gap)' ,
5353 'var(--my-gap, calc(2 * var(--gap)))' ,
@@ -195,6 +195,174 @@ describe('StyleProcessor', () => {
195195 ] ) ;
196196 } ) ;
197197
198+ test ( 'parses new custom property with fallback syntax' , ( ) => {
199+ const result = parser . process (
200+ '($custom-margin, 1x) ($theme-color, #purple)' ,
201+ ) ;
202+ expect ( result . groups [ 0 ] . values ) . toEqual ( [
203+ 'var(--custom-margin, var(--gap))' ,
204+ 'var(--theme-color, var(--purple-color))' ,
205+ ] ) ;
206+ } ) ;
207+
208+ test ( 'parses new custom property syntax in complex expressions' , ( ) => {
209+ const result = parser . process ( '(100% - (2 * ($custom-gap, 1x)))' ) ;
210+ expect ( result . groups [ 0 ] . values ) . toEqual ( [
211+ 'calc(100% - calc(2 * var(--custom-gap, var(--gap))))' ,
212+ ] ) ;
213+ } ) ;
214+
215+ test ( 'distinguishes between functions and custom property fallbacks' , ( ) => {
216+ // Test function with custom property as first argument
217+ const result1 = parser . process ( 'sum($my-prop, 2x)' ) ;
218+ expect ( result1 . groups [ 0 ] . values ) . toEqual ( [
219+ 'calc(var(--my-prop) + calc(2 * var(--gap)))' ,
220+ ] ) ;
221+
222+ // Test custom property with fallback
223+ const result2 = parser . process ( '($my-prop, 2x)' ) ;
224+ expect ( result2 . groups [ 0 ] . values ) . toEqual ( [
225+ 'var(--my-prop, calc(2 * var(--gap)))' ,
226+ ] ) ;
227+
228+ // Test multiple scenarios in one expression
229+ const result3 = parser . process ( 'sum($a, $b) ($fallback-prop, 1x)' ) ;
230+ expect ( result3 . groups [ 0 ] . values ) . toEqual ( [
231+ 'calc(var(--a) + var(--b))' ,
232+ 'var(--fallback-prop, var(--gap))' ,
233+ ] ) ;
234+
235+ // Test edge case: function with custom property fallback as argument
236+ const result4 = parser . process ( 'sum(($prop-a, 1x), ($prop-b, 2x))' ) ;
237+ expect ( result4 . groups [ 0 ] . values ) . toEqual ( [
238+ 'calc(var(--prop-a, var(--gap)) + var(--prop-b, calc(2 * var(--gap))))' ,
239+ ] ) ;
240+
241+ // Test color functions with custom properties
242+ const result5 = parser . process ( 'rgb($red, $green, $blue)' ) ;
243+ expect ( result5 . groups [ 0 ] . colors ) . toEqual ( [
244+ 'rgb(var(--red),var(--green),var(--blue))' ,
245+ ] ) ;
246+
247+ // Test generic function (not user-defined)
248+ const result6 = parser . process ( 'min($width, 100%)' ) ;
249+ expect ( result6 . groups [ 0 ] . values ) . toEqual ( [ 'min(var(--width), 100%)' ] ) ;
250+
251+ // Test critical edge case: ensure no ambiguity in parsing order
252+ // Function name 'sum' vs custom property fallback starting with 'sum'
253+ const result7 = parser . process ( 'sum($a, $b) ($sum, fallback)' ) ;
254+ expect ( result7 . groups [ 0 ] . values ) . toEqual ( [
255+ 'calc(var(--a) + var(--b))' , // Function call
256+ 'var(--sum, fallback)' , // Custom property fallback (not a function)
257+ ] ) ;
258+ } ) ;
259+
260+ test ( 'validates CSS custom property names correctly' , ( ) => {
261+ // Valid names
262+ const result1 = parser . process (
263+ '$valid-name $_underscore $hyphen-ok $abc123' ,
264+ ) ;
265+ expect ( result1 . groups [ 0 ] . values ) . toEqual ( [
266+ 'var(--valid-name)' ,
267+ 'var(--_underscore)' ,
268+ 'var(--hyphen-ok)' ,
269+ 'var(--abc123)' ,
270+ ] ) ;
271+
272+ // Invalid names (should become modifiers)
273+ const result2 = parser . process ( '$123invalid $-123invalid $0test $-' ) ;
274+ expect ( result2 . groups [ 0 ] . mods ) . toEqual ( [
275+ '$123invalid' ,
276+ '$-123invalid' ,
277+ '$0test' ,
278+ '$-' ,
279+ ] ) ;
280+
281+ // Edge case: single character names
282+ const result3 = parser . process ( '$a $_ $1' ) ;
283+ expect ( result3 . groups [ 0 ] . values ) . toEqual ( [ 'var(--a)' , 'var(--_)' ] ) ;
284+ expect ( result3 . groups [ 0 ] . mods ) . toEqual ( [ '$1' ] ) ;
285+ } ) ;
286+
287+ test ( 'comprehensive collision testing for edge cases' , ( ) => {
288+ // Test 1: Auto-calc vs custom property fallback - similar patterns
289+ const result1 = parser . process ( '(100% - 2x) ($gap, 1x)' ) ;
290+ expect ( result1 . groups [ 0 ] . values ) . toEqual ( [
291+ 'calc(100% - calc(2 * var(--gap)))' , // Auto-calc
292+ 'var(--gap, var(--gap))' , // Custom property fallback
293+ ] ) ;
294+
295+ // Test 2: URL with comma vs custom property fallback
296+ // NOTE: URLs merge with following tokens for background layers
297+ const result2 = parser . process (
298+ 'url("img,with,comma.png") ($fallback, auto)' ,
299+ ) ;
300+ expect ( result2 . groups [ 0 ] . values ) . toEqual ( [
301+ 'url("img,with,comma.png") var(--fallback, auto)' , // URL merges with following token
302+ ] ) ;
303+
304+ // Test 3: Quoted strings that look like custom properties
305+ const result3 = parser . process (
306+ '"($not-a-prop, value)" ($real-prop, fallback)' ,
307+ ) ;
308+ expect ( result3 . groups [ 0 ] . values ) . toEqual ( [
309+ '"($not-a-prop, value)"' , // Quoted string (not processed)
310+ 'var(--real-prop, fallback)' , // Custom property fallback
311+ ] ) ;
312+
313+ // Test 4: Color function with similar pattern
314+ const result4 = parser . process (
315+ 'rgb($red, $green, $blue) ($color-fallback, #fff)' ,
316+ ) ;
317+ expect ( result4 . groups [ 0 ] . colors ) . toEqual ( [
318+ 'rgb(var(--red),var(--green),var(--blue))' ,
319+ ] ) ;
320+ expect ( result4 . groups [ 0 ] . values ) . toEqual ( [
321+ 'var(--color-fallback, var(--fff-color, #fff))' ,
322+ ] ) ;
323+
324+ // Test 5: Nested parentheses with custom properties
325+ const result5 = parser . process ( '(($outer, 10px) + ($inner, 5px))' ) ;
326+ expect ( result5 . groups [ 0 ] . values ) . toEqual ( [
327+ 'calc(var(--outer, 10px) + var(--inner, 5px))' ,
328+ ] ) ;
329+
330+ // Test 6: Invalid custom property patterns (should not match)
331+ const result6 = parser . process (
332+ '(not-a-prop, value) (@invalid-syntax, bad)' ,
333+ ) ;
334+ expect ( result6 . groups [ 0 ] . values ) . toEqual ( [
335+ 'calc(not-a-prop, value)' , // Auto-calc (no $ prefix)
336+ 'var(--invalid-syntax, bad)' , // @ is valid custom property prefix
337+ ] ) ;
338+
339+ // Test 7: Edge case with spaces and special characters
340+ // NOTE: Extra spaces cause the pattern to not match, falling back to auto-calc
341+ const result7 = parser . process ( '( $spaced , fallback ) ($compact,nospace)' ) ;
342+ expect ( result7 . groups [ 0 ] . values ) . toEqual ( [
343+ 'calc(var(--spaced), fallback)' , // Extra spaces -> auto-calc, not custom property
344+ 'var(--compact, nospace)' , // No spaces are fine
345+ ] ) ;
346+
347+ // Test 8: Edge cases with regex boundaries
348+ // Now properly validates CSS custom property names
349+ const result8 = parser . process (
350+ '($123invalid, fallback) ($valid-name, fallback) ($_underscore, fallback) ($hyphen-ok, fallback)' ,
351+ ) ;
352+ expect ( result8 . groups [ 0 ] . values ) . toEqual ( [
353+ 'calc($123invalid, fallback)' , // Invalid (starts with number) -> auto-calc
354+ 'var(--valid-name, fallback)' , // Valid
355+ 'var(--_underscore, fallback)' , // Valid (underscore allowed)
356+ 'var(--hyphen-ok, fallback)' , // Valid (letter followed by hyphen)
357+ ] ) ;
358+
359+ // Test 9: Comma separation in complex scenarios
360+ const result9 = parser . process ( '($prop1, fallback), ($prop2, fallback)' ) ;
361+ expect ( result9 . groups . length ) . toBe ( 2 ) ; // Should create two groups
362+ expect ( result9 . groups [ 0 ] . values ) . toEqual ( [ 'var(--prop1, fallback)' ] ) ;
363+ expect ( result9 . groups [ 1 ] . values ) . toEqual ( [ 'var(--prop2, fallback)' ] ) ;
364+ } ) ;
365+
198366 test ( 'skips invalid functions while parsing (for example missing closing parenthesis)' , ( ) => {
199367 const warnSpy = jest . spyOn ( console , 'warn' ) . mockImplementation ( ( ) => { } ) ;
200368
0 commit comments