Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .config/jest.config.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ process.env.REACT_NATIVE_CSS_TEST_DEBUG = true;

module.exports = {
...jestExpo,
testPathIgnorePatterns: ["dist/"],
testPathIgnorePatterns: ["dist/", ".*/_[a-zA-Z]"],
setupFilesAfterEnv: ["./.config/jest.setup.js"],
};
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@
"@commitlint/config-conventional": "^19.8.1",
"@eslint/js": "^9.30.1",
"@ianvs/prettier-plugin-sort-imports": "^4.4.2",
"@tailwindcss/postcss": "^4.1.12",
"@testing-library/react-native": "^13.2.0",
"@tsconfig/react-native": "^3.0.6",
"@types/babel__core": "^7",
Expand All @@ -179,6 +180,7 @@
"lefthook": "^1.12.2",
"lightningcss": "^1.30.1",
"metro-runtime": "^0.83.0",
"postcss": "^8.5.6",
"prettier": "^3.6.2",
"react": "19.1.0",
"react-native": "0.80.1",
Expand Down
192 changes: 192 additions & 0 deletions src/__tests__/vendor/tailwind/_tailwind.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
import type { PropsWithChildren, ReactElement } from "react";

import tailwind from "@tailwindcss/postcss";
import {
screen,
render as tlRender,
type RenderOptions,
} from "@testing-library/react-native";
import postcss from "postcss";
import { registerCSS } from "react-native-css/jest";

import type { compile } from "../../../compiler";
import { View } from "../../../components";

const testID = "tailwind";

export type NativewindRenderOptions = RenderOptions & {
/** Replace the generated CSS*/
css?: string;
/** Appended after the generated CSS */
extraCss?: string;
/** Specify the className to use for the component @default sourceInline */
className?: string;
/** Add `@source inline('<className>')` to the CSS. @default Values are extracted from the component's className */
sourceInline?: string[];
/** Whether to include the theme in the generated CSS @default true */
theme?: boolean;
/** Whether to include the preflight in the generated CSS @default false */
preflight?: boolean;
/** Whether to include the plugin in the generated CSS. @default true */
plugin?: boolean;
/** Enable debug logging. @default false - Set process.env.NATIVEWIND_TEST_AUTO_DEBUG and run tests with the node inspector */
debug?: boolean;
};

const debugDefault = Boolean(process.env.NODE_OPTIONS?.includes("--inspect"));

export async function render(
component: ReactElement<PropsWithChildren>,
{
css,
sourceInline = Array.from(getClassNames(component)),
debug = debugDefault,
theme = true,
preflight = false,
extraCss,
...options
}: NativewindRenderOptions = {},
): Promise<ReturnType<typeof tlRender> & ReturnType<typeof compile>> {
if (!css) {
css = ``;

if (theme) {
css += `@import "tailwindcss/theme.css" layer(theme);\n`;
}

if (preflight) {
css += `@import "tailwindcss/preflight.css" layer(base);\n`;
}

css += `@import "tailwindcss/utilities.css" layer(utilities) source(none);\n`;
}

css += sourceInline
.map((source) => `@source inline("${source}");`)
.join("\n");

if (extraCss) {
css += `\n${extraCss}`;
}

if (debug) {
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,
});

if (debug) {
console.log(`Output CSS:\n---\n${output}\n---\n`);
}

const compiled = registerCSS(output, { debug });

return Object.assign(
{},
tlRender(component, {
...options,
}),
compiled,
);
}

render.debug = (
component: ReactElement<PropsWithChildren>,
options: RenderOptions = {},
) => {
return render(component, { ...options, debug: true });
};

function getClassNames(
component: ReactElement<PropsWithChildren>,
classNames = new Set<string>(),
) {
if (
typeof component.props === "object" &&
"className" in component.props &&
typeof component.props.className === "string"
) {
classNames.add(component.props.className);
}

if (component.props.children) {
const children: ReactElement[] = Array.isArray(component.props.children)
? component.props.children
: [component.props.children];

for (const child of children) {
getClassNames(child as ReactElement<PropsWithChildren>, classNames);
}
}

return classNames;
}

export async function renderSimple({
className,
...options
}: NativewindRenderOptions & { className: string }) {
const { warnings: warningFn } = await render(
<View testID={testID} className={className} />,
options,
);
const component = screen.getByTestId(testID, { hidden: true });

// Strip the testID and the children
const { testID: _testID, children, ...props } = component.props;

const compilerWarnings = warningFn();

let warnings: Record<string, unknown> | undefined;

if (compilerWarnings.properties) {
warnings ??= {};
warnings.properties = compilerWarnings.properties;
}

const warningValues = compilerWarnings.values;

if (warningValues) {
warnings ??= {};
warnings.values = Object.fromEntries(
Object.entries(warningValues).map(([key, value]) => [
key,
value.length > 1 ? value : value[0],
]),
);
}

return warnings ? { props, warnings } : { props };
}

renderSimple.debug = (
options: NativewindRenderOptions & { className: string },
) => {
return renderSimple({ ...options, debug: true });
};

/**
* Helper method that uses the current test name to render the component
* Doesn't not support multiple components or changing the component type
*/
export async function renderCurrentTest({
sourceInline = [expect.getState().currentTestName?.split(/\s+/).at(-1) ?? ""],
className = sourceInline.join(" "),
...options
}: NativewindRenderOptions = {}) {
return renderSimple({
...options,
sourceInline,
className,
});
}

renderCurrentTest.debug = (options: NativewindRenderOptions = {}) => {
return renderCurrentTest({ ...options, debug: true });
};
Loading
Loading