Skip to content
Open
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
425 changes: 425 additions & 0 deletions src/__tests__/compiler/directionality.test.ts

Large diffs are not rendered by default.

75 changes: 75 additions & 0 deletions src/__tests__/compiler/selectors.test.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { compile } from "react-native-css/compiler";

import { getClassNameSelectors } from "../../compiler/selector-builder";

test("empty", () => {
Expand Down Expand Up @@ -150,3 +152,76 @@ test(".group-[.test]:text-white { &:is(:where(.group):is(.test) *) {}", () => {
},
]);
});

// HTML compares a dir value ASCII case-insensitively unless the selector carries s
test.each([
['[dir="RTL"]', ["a", "dir", "=", "RTL", "i"]],
['[dir="rtl"]', ["a", "dir", "=", "rtl", "i"]],
['[dir="RTL" i]', ["a", "dir", "=", "RTL", "i"]],
['[dir="RTL" s]', ["a", "dir", "=", "RTL", "s"]],
["[dir]", ["a", "dir"]],
])(".t%s reads the element's own dir prop", (attribute, query) => {
const rule = compile(`.t${attribute} { color: red }`).stylesheet()
.s?.[0]?.[1][0];

expect(rule?.aq).toStrictEqual([query]);
});

test.each([
['[class="a b"]', ["a", "className", "=", "a b"]],
['[class~="b"]', ["a", "className", "~=", "b"]],
['[class*="b"]', ["a", "className", "*=", "b"]],
["[class]", ["a", "className"]],
['[data-state="open"]', ["d", "state", "=", "open"]],
["[disabled]", ["a", "disabled"]],
])(".t%s reads the prop it arrives on", (attribute, query) => {
const rule = compile(`.t${attribute} { color: red }`).stylesheet()
.s?.[0]?.[1][0];

expect(rule?.aq).toStrictEqual([query]);
});

test("an ancestor [class~=…] reads className through the :is() site", () => {
const rule = compile(
`.child:is(:where(.group)[class~="active"] *) { color: red }`,
)
.stylesheet()
.s?.find(([name]) => name === "child")?.[1][0];

expect(rule?.cq).toStrictEqual([
{ n: "g:group", a: [["a", "className", "~=", "active"]] },
]);
});

test.each([
['[data-x="a"]', ["test"]],
['[|data-x="a"]', ["test"]],
['[*|data-x="a"]', ["test"]],
['[undeclared|data-x="a"]', []],
])(".test%s emits the rules %p", (attribute, rules) => {
expect(
compile(`.test${attribute} { width: 10px }`)
.stylesheet()
.s?.map(([name]) => name) ?? [],
).toStrictEqual(rules);
});

test("a declared namespace prefix emits no rule, and a sibling rule survives", () => {
expect(
compile(
`@namespace ns url(http://example.com/ns); .dropped[ns|data-x="a"] { width: 10px } .kept[data-x="a"] { width: 10px }`,
)
.stylesheet()
.s?.map(([name]) => name),
).toStrictEqual(["kept"]);
});

test("an ancestor's namespace-qualified attribute emits no rule through the :is() site", () => {
expect(
compile(
`.child:is(:where(.group)[undeclared|data-x="a"] *) { width: 10px }`,
)
.stylesheet()
.s?.find(([name]) => name === "child"),
).toBeUndefined();
});
91 changes: 91 additions & 0 deletions src/__tests__/native/attributes.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -280,3 +280,94 @@ test.each(["=", "|=", "~=", "^=", "$=", "*="] as const)(
expect(screen.getByTestId(testID).props.style.width).toBe(40);
},
);

// react-native-web writes a dataSet value as a data-* attribute unless it is null or undefined
describe("a dataSet value projects as react-native-web writes it", () => {
test.each([
[false, true],
[0, true],
["", true],
[null, false],
[undefined, false],
])("[data-test] over %p is present: %s", (value, present) => {
registerCSS(`.test { width: 40px; } .test[data-test] { width: 80px; }`);
render(
<View
testID={testID}
className="test"
{...{ dataSet: { test: value } }}
/>,
);
expect(screen.getByTestId(testID).props.style.width).toBe(
present ? 80 : 40,
);
});

test("a false plain prop is an absent boolean attribute", () => {
registerCSS(`.test { width: 40px; } .test[disabled] { width: 80px; }`);
render(<View testID={testID} className="test" {...{ disabled: false }} />);
expect(screen.getByTestId(testID).props.style.width).toBe(40);
});

test.each([
['[data-test="a,b"]', true],
['[data-test~="a"]', false],
])("%s over the array [a, b] matches: %s", (attribute, matches) => {
registerCSS(`.test { width: 40px; } .test${attribute} { width: 80px; }`);
render(
<View
testID={testID}
className="test"
{...{ dataSet: { test: ["a", "b"] } }}
/>,
);
expect(screen.getByTestId(testID).props.style.width).toBe(
matches ? 80 : 40,
);
});
});

test.each([
["[class='test example']", "example", true],
["[class='example']", "example", false],
["[class~='example']", "example", true],
["[class~='example']", "other", false],
["[class*='xamp']", "example", true],
["[class]", "example", true],
])(
".test%s over the class list test %s matches: %s",
(selector, name, matches) => {
registerCSS(`.test { width: 40px; } .test${selector} { width: 80px; }`);
render(<View testID={testID} className={`test ${name}`} />);
expect(screen.getByTestId(testID).props.style.width).toBe(
matches ? 80 : 40,
);
},
);

test.each([
["group active", true],
["group", false],
])("an ancestor class list %p answers [class~=…]: %s", (name, matches) => {
registerCSS(
`.child { width: 40px; } .child:is(:where(.group)[class~="active"] *) { width: 80px; }`,
);
render(
<View className={name}>
<View testID={testID} className="child" />
</View>,
);
expect(screen.getByTestId(testID).props.style.width).toBe(matches ? 80 : 40);
});

test.each([
['[|data-x="a"]', true],
['[*|data-x="a"]', true],
['[undeclared|data-x="a"]', false],
])(".test%s over data-x=a matches: %s", (attribute, matches) => {
registerCSS(`.test { width: 40px; } .test${attribute} { width: 80px; }`);
render(
<View testID={testID} className="test" {...{ dataSet: { x: "a" } }} />,
);
expect(screen.getByTestId(testID).props.style.width).toBe(matches ? 80 : 40);
});
15 changes: 15 additions & 0 deletions src/__tests__/native/direction-media.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,18 @@ test.each([false, true])(
expect(screen.getByTestId("ltr")).toHaveStyle({ width: isRTL ? 10 : 37 });
},
);

test.each<[string, "rtl" | undefined, boolean]>([
['[dir="RTL"]', "rtl", true],
['[dir="RTL" i]', "rtl", true],
['[dir="RTL" s]', "rtl", false],
['[dir="rtl"]', undefined, false],
])(".t%s over the dir prop %p matches: %s", (attribute, direction, matches) => {
Object.defineProperty(I18nManager, "isRTL", {
configurable: true,
value: true,
});
registerCSS(`.t { width: 10px; } .t${attribute} { width: 37px; }`);
render(<View testID="t" className="t" dir={direction} />);
expect(screen.getByTestId("t")).toHaveStyle({ width: matches ? 37 : 10 });
});
Loading