Skip to content

Commit 7525fc2

Browse files
author
julusy
committed
fix(mobile): scope uniwind media queries without losing the enclosing class
The per-sibling media-query reset also wiped the outer declaration context, so media rules nested inside a class (@Utility bodies with @variant, nested breakpoints) stopped writing into that class and their declarations compiled as global vars or were dropped. Reset each sibling from the block's outer config (plus the block's media queries) and restore it after the block, matching the previous compiler for these shapes while keeping the platform guard fix. Add nested-shape fixture cases pinned to the pre-fix compiler output.
1 parent 5b2643e commit 7525fc2

3 files changed

Lines changed: 137 additions & 907 deletions

File tree

‎apps/mobile/src/lib/uniwind-platform-variants.test.ts‎

Lines changed: 85 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -52,36 +52,42 @@ const ANDROID_CLASSES = ["android:shrink", "android:grow-0", "android:font-mono"
5252
const SHARED_CLASSES = ["flex-1", "pt-12"];
5353
const 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+
5586
describe("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

Comments
 (0)