@@ -392,5 +392,172 @@ describe('Arbitrary Values and Properties', () => {
392392 expect ( gen . toCSS ( false ) ) . toContain ( 'transform: rotate(0.25turn);' )
393393 } )
394394 } )
395+
396+ describe ( 'CSS Selector Escaping' , ( ) => {
397+ it ( 'should properly escape calc() with viewport units' , ( ) => {
398+ const gen = new CSSGenerator ( defaultConfig )
399+ gen . generate ( 'h-[calc(100vh-4rem)]' )
400+ const css = gen . toCSS ( false )
401+ expect ( css ) . toContain ( 'height: calc(100vh-4rem);' )
402+ // Verify the selector is properly escaped with backslashes
403+ expect ( css ) . toContain ( '.h-\\[calc\\(100vh-4rem\\)\\]' )
404+ } )
405+
406+ it ( 'should properly escape calc() with percentage' , ( ) => {
407+ const gen = new CSSGenerator ( defaultConfig )
408+ gen . generate ( 'w-[calc(100%-2rem)]' )
409+ const css = gen . toCSS ( false )
410+ expect ( css ) . toContain ( 'width: calc(100%-2rem);' )
411+ expect ( css ) . toContain ( '.w-\\[calc\\(100\\%-2rem\\)\\]' )
412+ } )
413+
414+ it ( 'should properly escape min() function' , ( ) => {
415+ const gen = new CSSGenerator ( defaultConfig )
416+ gen . generate ( 'w-[min(100%,500px)]' )
417+ const css = gen . toCSS ( false )
418+ expect ( css ) . toContain ( 'width: min(100%,500px);' )
419+ expect ( css ) . toContain ( '.w-\\[min\\(100\\%\\,500px\\)\\]' )
420+ } )
421+
422+ it ( 'should properly escape max() function' , ( ) => {
423+ const gen = new CSSGenerator ( defaultConfig )
424+ gen . generate ( 'h-[max(50vh,300px)]' )
425+ const css = gen . toCSS ( false )
426+ expect ( css ) . toContain ( 'height: max(50vh,300px);' )
427+ expect ( css ) . toContain ( '.h-\\[max\\(50vh\\,300px\\)\\]' )
428+ } )
429+
430+ it ( 'should properly escape clamp() function' , ( ) => {
431+ const gen = new CSSGenerator ( defaultConfig )
432+ gen . generate ( 'text-[clamp(1rem,2.5vw,3rem)]' )
433+ const css = gen . toCSS ( false )
434+ expect ( css ) . toContain ( 'font-size: clamp(1rem,2.5vw,3rem);' )
435+ expect ( css ) . toContain ( '.text-\\[clamp\\(1rem\\,2\\.5vw\\,3rem\\)\\]' )
436+ } )
437+
438+ it ( 'should properly escape nested functions' , ( ) => {
439+ const gen = new CSSGenerator ( defaultConfig )
440+ gen . generate ( '[grid-template-columns:repeat(auto-fit,minmax(200px,1fr))]' )
441+ const css = gen . toCSS ( false )
442+ expect ( css ) . toContain ( 'grid-template-columns: repeat(auto-fit,minmax(200px,1fr));' )
443+ // The selector should have all parentheses and commas escaped
444+ expect ( css ) . toMatch ( / \. \\ \[ g r i d - t e m p l a t e - c o l u m n s \\ : r e p e a t \\ \( a u t o - f i t \\ , m i n m a x \\ \( 2 0 0 p x \\ , 1 f r \\ \) \\ \) \\ \] / )
445+ } )
446+
447+ it ( 'should properly escape rgba colors' , ( ) => {
448+ const gen = new CSSGenerator ( defaultConfig )
449+ gen . generate ( 'bg-[rgba(0,0,0,0.5)]' )
450+ const css = gen . toCSS ( false )
451+ expect ( css ) . toContain ( 'background-color: rgba(0,0,0,0.5);' )
452+ expect ( css ) . toContain ( '.bg-\\[rgba\\(0\\,0\\,0\\,0\\.5\\)\\]' )
453+ } )
454+
455+ it ( 'should properly escape hsla colors' , ( ) => {
456+ const gen = new CSSGenerator ( defaultConfig )
457+ gen . generate ( 'bg-[hsla(120,50%,50%,0.8)]' )
458+ const css = gen . toCSS ( false )
459+ expect ( css ) . toContain ( 'background-color: hsla(120,50%,50%,0.8);' )
460+ expect ( css ) . toContain ( '.bg-\\[hsla\\(120\\,50\\%\\,50\\%\\,0\\.8\\)\\]' )
461+ } )
462+
463+ it ( 'should properly escape hash in hex colors' , ( ) => {
464+ const gen = new CSSGenerator ( defaultConfig )
465+ gen . generate ( 'bg-[#ff6600]' )
466+ const css = gen . toCSS ( false )
467+ expect ( css ) . toContain ( 'background-color: #ff6600;' )
468+ expect ( css ) . toContain ( '.bg-\\[\\#ff6600\\]' )
469+ } )
470+
471+ it ( 'should handle complex calc with multiple operations' , ( ) => {
472+ const gen = new CSSGenerator ( defaultConfig )
473+ gen . generate ( 'w-[calc((100vw-64px)/2)]' )
474+ const css = gen . toCSS ( false )
475+ expect ( css ) . toContain ( 'width: calc((100vw-64px)/2);' )
476+ } )
477+
478+ it ( 'should handle sidebar-style height calculation' , ( ) => {
479+ // This is the specific use case that triggered the fix
480+ const gen = new CSSGenerator ( defaultConfig )
481+ gen . generate ( 'h-[calc(100vh-4rem)]' )
482+ const css = gen . toCSS ( false )
483+ // Must contain valid CSS
484+ expect ( css ) . toContain ( 'height: calc(100vh-4rem);' )
485+ // Selector must be properly escaped so browser can match it
486+ expect ( css ) . toMatch ( / \. h - \\ \[ c a l c \\ \( 1 0 0 v h - 4 r e m \\ \) \\ \] / )
487+ } )
488+
489+ it ( 'should handle min-height with calc' , ( ) => {
490+ const gen = new CSSGenerator ( defaultConfig )
491+ gen . generate ( 'min-h-[calc(100vh-64px)]' )
492+ const css = gen . toCSS ( false )
493+ expect ( css ) . toContain ( 'min-height: calc(100vh-64px);' )
494+ } )
495+
496+ it ( 'should handle max-width with calc' , ( ) => {
497+ const gen = new CSSGenerator ( defaultConfig )
498+ gen . generate ( 'max-w-[calc(100%-2rem)]' )
499+ const css = gen . toCSS ( false )
500+ expect ( css ) . toContain ( 'max-width: calc(100%-2rem);' )
501+ } )
502+
503+ it ( 'should escape special characters with variants' , ( ) => {
504+ const gen = new CSSGenerator ( defaultConfig )
505+ gen . generate ( 'lg:h-[calc(100vh-4rem)]' )
506+ const css = gen . toCSS ( false )
507+ expect ( css ) . toContain ( '@media (min-width: 1024px)' )
508+ expect ( css ) . toContain ( 'height: calc(100vh-4rem);' )
509+ } )
510+
511+ it ( 'should escape special characters with hover variant' , ( ) => {
512+ const gen = new CSSGenerator ( defaultConfig )
513+ gen . generate ( 'hover:bg-[rgba(0,0,0,0.1)]' )
514+ const css = gen . toCSS ( false )
515+ expect ( css ) . toContain ( ':hover' )
516+ expect ( css ) . toContain ( 'background-color: rgba(0,0,0,0.1);' )
517+ } )
518+
519+ it ( 'should handle CSS var with fallback containing comma' , ( ) => {
520+ const gen = new CSSGenerator ( defaultConfig )
521+ gen . generate ( '[color:var(--primary,#000)]' )
522+ const css = gen . toCSS ( false )
523+ expect ( css ) . toContain ( 'color: var(--primary,#000);' )
524+ } )
525+
526+ it ( 'should handle transform with translate' , ( ) => {
527+ const gen = new CSSGenerator ( defaultConfig )
528+ gen . generate ( '[transform:translateX(calc(100%+1rem))]' )
529+ const css = gen . toCSS ( false )
530+ expect ( css ) . toContain ( 'transform: translateX(calc(100%+1rem));' )
531+ } )
532+
533+ it ( 'should handle filter with multiple functions' , ( ) => {
534+ const gen = new CSSGenerator ( defaultConfig )
535+ gen . generate ( '[filter:blur(4px)brightness(0.9)]' )
536+ const css = gen . toCSS ( false )
537+ expect ( css ) . toContain ( 'filter: blur(4px)brightness(0.9);' )
538+ } )
539+
540+ it ( 'should properly escape plus sign in selectors' , ( ) => {
541+ const gen = new CSSGenerator ( defaultConfig )
542+ gen . generate ( '[margin:calc(1rem+2px)]' )
543+ const css = gen . toCSS ( false )
544+ expect ( css ) . toContain ( 'margin: calc(1rem+2px);' )
545+ expect ( css ) . toContain ( '.\\[margin\\:calc\\(1rem\\+2px\\)\\]' )
546+ } )
547+
548+ it ( 'should properly escape tilde in selectors' , ( ) => {
549+ const gen = new CSSGenerator ( defaultConfig )
550+ gen . generate ( '[content:~test]' )
551+ const css = gen . toCSS ( false )
552+ expect ( css ) . toContain ( '.\\[content\\:\\~test\\]' )
553+ } )
554+
555+ it ( 'should properly escape greater than sign in selectors' , ( ) => {
556+ const gen = new CSSGenerator ( defaultConfig )
557+ gen . generate ( '[content:a>b]' )
558+ const css = gen . toCSS ( false )
559+ expect ( css ) . toContain ( '.\\[content\\:a\\>b\\]' )
560+ } )
561+ } )
395562 } )
396563} )
0 commit comments