Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@
"@react-native/jest-preset": "0.86.3",
"@react-native/metro-config": "0.86.3",
"@release-it/conventional-changelog": "10.0.1",
"@tailwindcss/postcss": "^4.1.12",
"@tailwindcss/node": "^4.1.12",
"@testing-library/react-native": "^13.3.3",
"@tsconfig/react-native": "^3.0.6",
"@types/babel__core": "^7",
Expand All @@ -266,7 +266,6 @@
"lefthook": "^2.1.5",
"lightningcss": "^1.30.1",
"metro-runtime": "^0.84.2",
"postcss": "^8.5.6",
"prettier": "^3.6.2",
"react": "19.2.3",
"react-native": "0.86.3",
Expand Down
33 changes: 24 additions & 9 deletions src/__tests__/vendor/tailwind/_tailwind.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import type { PropsWithChildren, ReactElement } from "react";

import tailwind from "@tailwindcss/postcss";
import { compile as compileTailwindCss } from "@tailwindcss/node";
import {
screen,
render as tlRender,
type RenderOptions,
} from "@testing-library/react-native";
import postcss from "postcss";
import type { compile } from "react-native-css/compiler";
import { View } from "react-native-css/components";
import { registerCSS } from "react-native-css/jest";
Expand Down Expand Up @@ -74,13 +73,7 @@ export async function render(
console.log(`Input CSS:\n---\n${css}\n---\n`);
}

// Process the TailwindCSS
const { css: output } = await postcss([
/* Tailwind seems to internally cache things, so we need a random value to cache bust */
tailwind({ base: Date.now().toString() }),
]).process(css, {
from: __dirname,
});
const output = await compileTailwind(css);

if (debug) {
console.log(`Output CSS:\n---\n${output}\n---\n`);
Expand All @@ -97,6 +90,28 @@ export async function render(
);
}

// @tailwindcss/postcss keeps one compiler per from and base and reuses it for other CSS until a file it read changes, and only logs a failed compile, so each render compiles with the compiler it wraps
// https://github.com/tailwindlabs/tailwindcss/blob/v4.1.13/packages/@tailwindcss-postcss/src/index.ts#L32-L33
// https://github.com/tailwindlabs/tailwindcss/blob/v4.1.13/packages/@tailwindcss-postcss/src/index.ts#L182-L196
// https://github.com/tailwindlabs/tailwindcss/blob/v4.1.13/packages/@tailwindcss-postcss/src/index.ts#L334-L352
async function compileTailwind(css: string): Promise<string> {
const compiler = await compileTailwindCss(css, {
base: __dirname,
onDependency: () => undefined,
});

if (
compiler.sources.some((source) => !source.negated) ||
(compiler.root !== null && compiler.root !== "none")
) {
throw new Error(
`The harness builds only the classes of @source inline(), and this CSS names a file source: ${JSON.stringify({ sources: compiler.sources, root: compiler.root })}`,
);
}

return compiler.build([]);
}

render.debug = (
component: ReactElement<PropsWithChildren>,
options: RenderOptions = {},
Expand Down
70 changes: 70 additions & 0 deletions src/__tests__/vendor/tailwind/harness.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { View } from "react-native-css/components";

import { render } from "./_tailwind";

test("renders that read the same millisecond compile and apply their own classes", async () => {
const now = jest.spyOn(Date, "now").mockReturnValue(0);

try {
for (const [className, style] of [
["left-0", { left: 0 }],
["left-1", { left: 3.5 }],
] as const) {
const { stylesheet, getByTestId } = await render(
<View testID={className} className={className} />,
);

expect(stylesheet().s?.map(([name]) => name)).toStrictEqual([className]);
expect(getByTestId(className).props.style).toStrictEqual(style);
}
} finally {
now.mockRestore();
}
});

test("a render whose CSS fails to compile rejects", async () => {
await expect(
render(<View className="left-0" />, { css: '@import "./missing.css";' }),
).rejects.toThrow("Can't resolve './missing.css'");
});

test("a render after one whose CSS uses no Tailwind feature compiles its own classes", async () => {
// A millisecond no other case reads, so only this case's renders could share a compiler
const now = jest.spyOn(Date, "now").mockReturnValue(1);

try {
await render(<View className="left-0" />, {
css: "@theme { --color-brand: red; }",
});
const { stylesheet } = await render(<View className="left-1" />);

expect(stylesheet().s?.map(([name]) => name)).toStrictEqual(["left-1"]);
} finally {
now.mockRestore();
}
});

test.each([
["an @source path", { extraCss: '@source "./";' }],
[
"a source() root",
{
css: '@import "tailwindcss/utilities.css" layer(utilities) source("./");',
},
],
])(
"a render whose CSS names a file source through %s rejects",
async (_sourceKind, options) => {
await expect(render(<View className="left-0" />, options)).rejects.toThrow(
"names a file source",
);
},
);

test("a render whose CSS only excludes a path compiles its classes", async () => {
const { stylesheet } = await render(<View className="left-0" />, {
extraCss: '@source not "./";',
});

expect(stylesheet().s?.map(([name]) => name)).toStrictEqual(["left-0"]);
});
9 changes: 4 additions & 5 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4501,7 +4501,7 @@ __metadata:
languageName: node
linkType: hard

"@tailwindcss/node@npm:4.1.13":
"@tailwindcss/node@npm:4.1.13, @tailwindcss/node@npm:^4.1.12":
version: 4.1.13
resolution: "@tailwindcss/node@npm:4.1.13"
dependencies:
Expand Down Expand Up @@ -4654,7 +4654,7 @@ __metadata:
languageName: node
linkType: hard

"@tailwindcss/postcss@npm:^4.1.11, @tailwindcss/postcss@npm:^4.1.12":
"@tailwindcss/postcss@npm:^4.1.11":
version: 4.1.13
resolution: "@tailwindcss/postcss@npm:4.1.13"
dependencies:
Expand Down Expand Up @@ -11824,7 +11824,7 @@ __metadata:
languageName: node
linkType: hard

"postcss@npm:^8.4.41, postcss@npm:^8.5.6":
"postcss@npm:^8.4.41":
version: 8.5.19
resolution: "postcss@npm:8.5.19"
dependencies:
Expand Down Expand Up @@ -12171,7 +12171,7 @@ __metadata:
"@react-native/jest-preset": "npm:0.86.3"
"@react-native/metro-config": "npm:0.86.3"
"@release-it/conventional-changelog": "npm:10.0.1"
"@tailwindcss/postcss": "npm:^4.1.12"
"@tailwindcss/node": "npm:^4.1.12"
"@testing-library/react-native": "npm:^13.3.3"
"@tsconfig/react-native": "npm:^3.0.6"
"@types/babel__core": "npm:^7"
Expand All @@ -12196,7 +12196,6 @@ __metadata:
lefthook: "npm:^2.1.5"
lightningcss: "npm:^1.30.1"
metro-runtime: "npm:^0.84.2"
postcss: "npm:^8.5.6"
prettier: "npm:^3.6.2"
react: "npm:19.2.3"
react-native: "npm:0.86.3"
Expand Down