@@ -52,36 +52,42 @@ const ANDROID_CLASSES = ["android:shrink", "android:grow-0", "android:font-mono"
5252const SHARED_CLASSES = [ "flex-1" , "pt-12" ] ;
5353const RESPONSIVE_CLASSES = [ "sm:p-6" , "sm:text-lg" ] ;
5454
55+ const runFixture = ( globalCss : string , probeSource : string ) : FixtureOutput => {
56+ const tempDir = NodeFS . mkdtempSync ( NodePath . join ( NodeOS . tmpdir ( ) , "uniwind-platform-" ) ) ;
57+ try {
58+ // So `@import "tailwindcss"` and `@import "uniwind"` resolve like in the app.
59+ NodeFS . symlinkSync (
60+ NodeFS . realpathSync ( new URL ( "../../node_modules" , import . meta. url ) ) ,
61+ NodePath . join ( tempDir , "node_modules" ) ,
62+ "dir" ,
63+ ) ;
64+ NodeFS . writeFileSync ( NodePath . join ( tempDir , "global.css" ) , globalCss ) ;
65+ NodeFS . writeFileSync ( NodePath . join ( tempDir , "Probe.tsx" ) , probeSource ) ;
66+
67+ const fixture = NodeURL . fileURLToPath (
68+ new URL ( "./uniwind-platform-variants.fixture.cjs" , import . meta. url ) ,
69+ ) ;
70+ const stdout = NodeChildProcess . execFileSync ( process . execPath , [ fixture , tempDir ] , {
71+ encoding : "utf8" ,
72+ maxBuffer : 32 * 1024 * 1024 ,
73+ timeout : 60_000 ,
74+ } ) ;
75+ // The fixture's compiler may log warnings to stdout before the JSON payload.
76+ const start = stdout . indexOf ( '{"tailwindChecks"' ) ;
77+ if ( start === - 1 ) {
78+ throw new Error ( `Fixture produced no JSON:\n${ stdout } ` ) ;
79+ }
80+ return JSON . parse ( stdout . slice ( start ) ) as FixtureOutput ;
81+ } finally {
82+ NodeFS . rmSync ( tempDir , { recursive : true , force : true } ) ;
83+ }
84+ } ;
85+
5586describe ( "uniwind platform variants compile per platform" , ( ) => {
5687 let output : FixtureOutput ;
5788
5889 beforeAll ( ( ) => {
59- const tempDir = NodeFS . mkdtempSync ( NodePath . join ( NodeOS . tmpdir ( ) , "uniwind-platform-" ) ) ;
60- try {
61- // So `@import "tailwindcss"` and `@import "uniwind"` resolve like in the app.
62- NodeFS . symlinkSync (
63- NodeFS . realpathSync ( new URL ( "../../node_modules" , import . meta. url ) ) ,
64- NodePath . join ( tempDir , "node_modules" ) ,
65- "dir" ,
66- ) ;
67- NodeFS . writeFileSync (
68- NodePath . join ( tempDir , "global.css" ) ,
69- '@import "tailwindcss";\n@import "uniwind";\n' ,
70- ) ;
71- NodeFS . writeFileSync ( NodePath . join ( tempDir , "Probe.tsx" ) , FIXTURE_SOURCE ) ;
72-
73- const fixture = NodeURL . fileURLToPath (
74- new URL ( "./uniwind-platform-variants.fixture.cjs" , import . meta. url ) ,
75- ) ;
76- const stdout = NodeChildProcess . execFileSync ( process . execPath , [ fixture , tempDir ] , {
77- encoding : "utf8" ,
78- maxBuffer : 32 * 1024 * 1024 ,
79- timeout : 60_000 ,
80- } ) ;
81- output = JSON . parse ( stdout ) as FixtureOutput ;
82- } finally {
83- NodeFS . rmSync ( tempDir , { recursive : true , force : true } ) ;
84- }
90+ output = runFixture ( '@import "tailwindcss";\n@import "uniwind";\n' , FIXTURE_SOURCE ) ;
8591 } , 60_000 ) ;
8692
8793 it ( "compiles the fixture utilities into shared platform blocks" , ( ) => {
@@ -146,3 +152,56 @@ describe("uniwind platform variants compile per platform", () => {
146152 }
147153 } ) ;
148154} ) ;
155+
156+ // The media-query fix must not lose the surrounding rule context: Tailwind
157+ // also emits media rules *nested inside a class rule* (`@utility` bodies with
158+ // `@variant`, nested breakpoints), and those declarations belong to the
159+ // enclosing class. These expectations match the pre-fix compiler output for
160+ // the same inputs, captured against the unpatched package.
161+ describe ( "uniwind keeps media rules nested inside class rules attached to the class" , ( ) => {
162+ let nested : FixtureOutput ;
163+
164+ beforeAll ( ( ) => {
165+ nested = runFixture (
166+ [
167+ '@import "tailwindcss";' ,
168+ '@import "uniwind";' ,
169+ "" ,
170+ ".container-x {" ,
171+ " width: 100%;" ,
172+ " @media (width >= 40rem) {" ,
173+ " max-width: 40rem;" ,
174+ " }" ,
175+ "}" ,
176+ "" ,
177+ "@utility foo-x {" ,
178+ " padding: 2px;" ,
179+ " @variant ios {" ,
180+ " padding: 3px;" ,
181+ " }" ,
182+ "}" ,
183+ "" ,
184+ ] . join ( "\n" ) ,
185+ 'export const Probe = () => <div className="container-x foo-x" />;\n' ,
186+ ) ;
187+ } , 60_000 ) ;
188+
189+ it ( "keeps the nested breakpoint entry of a nested media rule" , ( ) => {
190+ for ( const platform of [ "ios" , "android" ] ) {
191+ const entries = nested . platforms [ platform ] ?. styles [ "container-x" ] ;
192+ expect ( entries , `${ platform } lacks container-x` ) . toBeDefined ( ) ;
193+ expect ( entries ?. some ( ( style ) => ! style . native && style . minWidth === 0 ) ) . toBe ( true ) ;
194+ expect ( entries ?. some ( ( style ) => ! style . native && style . minWidth > 0 ) ) . toBe ( true ) ;
195+ }
196+ } ) ;
197+
198+ it ( "keeps @variant declarations inside @utility on their platform" , ( ) => {
199+ const iosEntries = nested . platforms . ios ?. styles [ "foo-x" ] ;
200+ expect ( iosEntries ?. some ( ( style ) => ! style . native ) ) . toBe ( true ) ;
201+ expect ( iosEntries ?. some ( ( style ) => style . native ) ) . toBe ( true ) ;
202+
203+ const androidEntries = nested . platforms . android ?. styles [ "foo-x" ] ;
204+ expect ( androidEntries ?. some ( ( style ) => ! style . native ) ) . toBe ( true ) ;
205+ expect ( androidEntries ?. some ( ( style ) => style . native ) ) . toBe ( false ) ;
206+ } ) ;
207+ } ) ;
0 commit comments