Skip to content

matrix(), matrix3d() and the 3D transform functions compile to an empty entry, so React Native's processTransform throws on the whole transform #553

Open

Description

@YevheniiKotyrlo

Before submitting a new issue

  • I tested using the latest version of the library, as the bug might be already fixed.
  • I tested using a supported version of react native.
  • I checked for possible duplicate issues, with possible answers.

Bug summary

transform: matrix(1, 0, 0, 1, 5, 6) renders transform: [[]]. Each of matrix(), matrix3d(), translate3d(), translateZ(), scale3d(), scaleZ() and rotate3d() becomes an empty list in the transform, and React Native's processTransform refuses it with You must specify exactly one property per transform object. Passed properties: [], so the whole transform throws in development, a rotate(45deg) beside it included.

Four of them have a form React Native's transform draws, and processTransform takes each:

CSS React Native
matrix(a, b, c, d, e, f) { matrix: [a, b, 0, c, d, 0, e, f, 1] }
matrix3d(…16 numbers) { matrix: [the 16 numbers] }
translate3d(x, y, z) { translate: [x, y, z] }
translateZ(z) { translate: [0, 0, z] }

React Native takes a matrix entry only as the transform's one entry, so a matrix() beside other functions would need them folded into it. scale3d(), scaleZ() and rotate3d() have no React Native form, but leaving them out of the list would let the rest of the transform apply, rather than the empty entry that makes it throw.

parseTransform (src/compiler/declarations.ts) returns [[]] from its translate3d, translateZ, scaleZ, scale3d, rotate3d, matrix and matrix3d arms. #389 changes those arms to return nothing, which stops the throw but still drops matrix(), matrix3d(), translate3d() and translateZ().

Library version

main at a37903f (0.0.0-nightly.a37903f)

Environment info

Windows 11 10.0.26300, Node 26.12.1, Yarn 4.9.2, react-native 0.86.3 (the repository's dev dependency).
No app or native build is involved: the output comes from the repository's jest setup, which runs React Native's own processTransform.

Steps to reproduce

  1. Clone the reproducer branch, which is main at a37903f with one file added, src/__tests__/native/matrix-and-3d-transforms.test.tsx, and install:
    git clone --branch repro/553-matrix-and-3d-transforms-dropped https://github.com/YevheniiKotyrlo/react-native-css
    cd react-native-css
    yarn install
  2. Run yarn jest src/__tests__/native/matrix-and-3d-transforms.test.tsx and read the logged lines: each CSS case's rendered style and whether processTransform takes its transform, then the same check for React Native's own forms.

src/__tests__/native/matrix-and-3d-transforms.test.tsx:

import { StyleSheet } from "react-native";

import { render, screen } from "@testing-library/react-native";
import { View } from "react-native-css/components/View";
import { registerCSS, testID } from "react-native-css/jest";

const processTransform = jest.requireActual<{
  default: (transform: unknown) => unknown;
}>("react-native/Libraries/StyleSheet/processTransform");

jest.mock("../../native/reanimated", () => ({
  animatedComponentFamily: (component: unknown) => component,
}));

function readByReactNative(transform: unknown): string {
  try {
    processTransform.default(transform);
    return "takes it";
  } catch (error) {
    return `throws ${(error as Error).message.split("\n")[0]}`;
  }
}

test.each([
  "matrix(1, 0, 0, 1, 5, 6)",
  "matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 5, 6, 7, 1)",
  "translate3d(1px, 2px, 3px)",
  "translateZ(10px)",
  "translate3d(1px, 2px, 3px) rotate(45deg)",
  "scale3d(1, 2, 3) rotate(45deg)",
  "scaleZ(2) rotate(45deg)",
  "rotate3d(0, 0, 1, 45deg) rotate(45deg)",
])("transform: %s", (transform) => {
  registerCSS(`.a { transform: ${transform}; }`);
  render(<View testID={testID} className="a" />);
  const style = StyleSheet.flatten(screen.getByTestId(testID).props.style);
  console.log(
    `transform: ${transform}\n   => ${JSON.stringify(style)}\n   processTransform: ${readByReactNative(style.transform)}`,
  );
  screen.unmount();
});

test.each([
  [[{ matrix: [1, 0, 0, 0, 1, 0, 5, 6, 1] }]],
  [[{ matrix: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 5, 6, 7, 1] }]],
  [[{ translate: [1, 2, 3] }]],
  [[{ translate: [0, 0, 10] }]],
  [[{ translate: [1, 2, 3] }, { rotate: "45deg" }]],
  [[{ rotate: "45deg" }]],
])("React Native's form %j", (transform) => {
  console.log(
    `${JSON.stringify(transform)}\n   processTransform: ${readByReactNative(transform)}`,
  );
});

Expected: matrix(1, 0, 0, 1, 5, 6) renders { matrix: [1, 0, 0, 0, 1, 0, 5, 6, 1] }, translate3d(1px, 2px, 3px) renders { translate: [1, 2, 3] } and translateZ(10px) renders { translate: [0, 0, 10] }, forms processTransform takes, as the second test shows; and no case renders an empty entry.

Actual: every case renders an empty entry ([[]], or [[], { rotate: "45deg" }] beside a rotate), and processTransform throws on it.

The run's output on main at a37903f, Windows 11, Node 26.12.1
  console.log
    transform: matrix(1, 0, 0, 1, 5, 6)
       => {"transform":[[]]}
       processTransform: throws You must specify exactly one property per transform object. Passed properties: []

      at log (src/__tests__/native/matrix-and-3d-transforms.test.tsx:37:11)

  console.log
    transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 5, 6, 7, 1)
       => {"transform":[[]]}
       processTransform: throws You must specify exactly one property per transform object. Passed properties: []

      at log (src/__tests__/native/matrix-and-3d-transforms.test.tsx:37:11)

  console.log
    transform: translate3d(1px, 2px, 3px)
       => {"transform":[[]]}
       processTransform: throws You must specify exactly one property per transform object. Passed properties: []

      at log (src/__tests__/native/matrix-and-3d-transforms.test.tsx:37:11)

  console.log
    transform: translateZ(10px)
       => {"transform":[[]]}
       processTransform: throws You must specify exactly one property per transform object. Passed properties: []

      at log (src/__tests__/native/matrix-and-3d-transforms.test.tsx:37:11)

  console.log
    transform: translate3d(1px, 2px, 3px) rotate(45deg)
       => {"transform":[[],{"rotate":"45deg"}]}
       processTransform: throws You must specify exactly one property per transform object. Passed properties: []

      at log (src/__tests__/native/matrix-and-3d-transforms.test.tsx:37:11)

  console.log
    transform: scale3d(1, 2, 3) rotate(45deg)
       => {"transform":[[],{"rotate":"45deg"}]}
       processTransform: throws You must specify exactly one property per transform object. Passed properties: []

      at log (src/__tests__/native/matrix-and-3d-transforms.test.tsx:37:11)

  console.log
    transform: scaleZ(2) rotate(45deg)
       => {"transform":[[],{"rotate":"45deg"}]}
       processTransform: throws You must specify exactly one property per transform object. Passed properties: []

      at log (src/__tests__/native/matrix-and-3d-transforms.test.tsx:37:11)

  console.log
    transform: rotate3d(0, 0, 1, 45deg) rotate(45deg)
       => {"transform":[[],{"rotate":"45deg"}]}
       processTransform: throws You must specify exactly one property per transform object. Passed properties: []

      at log (src/__tests__/native/matrix-and-3d-transforms.test.tsx:37:11)

  console.log
    [{"matrix":[1,0,0,0,1,0,5,6,1]}]
       processTransform: takes it

      at log (src/__tests__/native/matrix-and-3d-transforms.test.tsx:51:11)

  console.log
    [{"matrix":[1,0,0,0,0,1,0,0,0,0,1,0,5,6,7,1]}]
       processTransform: takes it

      at log (src/__tests__/native/matrix-and-3d-transforms.test.tsx:51:11)

  console.log
    [{"translate":[1,2,3]}]
       processTransform: takes it

      at log (src/__tests__/native/matrix-and-3d-transforms.test.tsx:51:11)

  console.log
    [{"translate":[0,0,10]}]
       processTransform: takes it

      at log (src/__tests__/native/matrix-and-3d-transforms.test.tsx:51:11)

  console.log
    [{"translate":[1,2,3]},{"rotate":"45deg"}]
       processTransform: takes it

      at log (src/__tests__/native/matrix-and-3d-transforms.test.tsx:51:11)

  console.log
    [{"rotate":"45deg"}]
       processTransform: takes it

      at log (src/__tests__/native/matrix-and-3d-transforms.test.tsx:51:11)

PASS src/__tests__/native/matrix-and-3d-transforms.test.tsx
  √ transform: matrix(1, 0, 0, 1, 5, 6) (25 ms)
  √ transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 5, 6, 7, 1) (5 ms)
  √ transform: translate3d(1px, 2px, 3px) (3 ms)
  √ transform: translateZ(10px) (1 ms)
  √ transform: translate3d(1px, 2px, 3px) rotate(45deg) (2 ms)
  √ transform: scale3d(1, 2, 3) rotate(45deg) (1 ms)
  √ transform: scaleZ(2) rotate(45deg) (1 ms)
  √ transform: rotate3d(0, 0, 1, 45deg) rotate(45deg) (1 ms)
  √ React Native's form [{"matrix":[1,0,0,0,1,0,5,6,1]}] (1 ms)
  √ React Native's form [{"matrix":[1,0,0,0,0,1,0,0,0,0,1,0,5,6,7,1]}] (1 ms)
  √ React Native's form [{"translate":[1,2,3]}]
  √ React Native's form [{"translate":[0,0,10]}]
  √ React Native's form [{"translate":[1,2,3]},{"rotate":"45deg"}] (1 ms)
  √ React Native's form [{"rotate":"45deg"}]

Test Suites: 1 passed, 1 total
Tests:       14 passed, 14 total
Snapshots:   0 total
Time:        1.649 s, estimated 2 s
Ran all test suites matching /src\\__tests__\\native\\matrix-and-3d-transforms.test.tsx/i.

Reproducible example repository

https://github.com/YevheniiKotyrlo/react-native-css/tree/repro/553-matrix-and-3d-transforms-dropped

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions