# Testing screens

> Test screens that contain a Scene with Jest and jest-expo, without a GPU or three.js. Set up Jest to compile Apricot, mock it with @apricot/react/mock, and know what the mock renders and returns.

Screens with a `<Scene>` can be tested with Jest (`jest-expo`) and React Native Testing Library. Tests run in Node, with no GPU, so mock Apricot with **`@apricot/react/mock`**:

- `<Scene>` is a plain `View` with the testID `apricot-scene`.
- 3D components draw nothing but render their children, so your own components and the text in a `<Label>` are still there to query.
- Hooks return values that do nothing (`useFrame` does nothing, `useModel` returns no object).
- `useLayout` and `fileStorage` are the real ones, so layout logic (add, move, undo, saving) can be tested for real.

## Setup

Apricot ships TypeScript sources, so let Jest compile it:

```js title="jest.config.js"
module.exports = {
  preset: "jest-expo",
  transformIgnorePatterns: [
    "node_modules/(?!((jest-)?react-native|@react-native(-community)?)|expo(nent)?|@expo(nent)?/.*|@apricot/.*)",
  ],
};
```

Mock the entry points your screens use, in each test file or once in Jest's setup file:

```js title="jest.setup.js"
jest.mock("@apricot/react", () => require("@apricot/react/mock"));
jest.mock("@apricot/react/model", () => require("@apricot/react/mock"));
jest.mock("@apricot/react/capture", () => require("@apricot/react/mock"));
```

## A test

```tsx title="Showroom.test.tsx"
import { fireEvent, render, screen } from "@testing-library/react-native";
import { Showroom } from "./Showroom";

jest.mock("@apricot/react", () => require("@apricot/react/mock"));
jest.mock("@apricot/react/model", () => require("@apricot/react/mock"));

test("shows the scene and the colour picker", () => {
  render(<Showroom />);
  expect(screen.getByTestId("apricot-scene")).toBeTruthy();
  fireEvent.press(screen.getByText("Red"));
  expect(screen.getByText("Red")).toBeTruthy();
});
```

## What the mock returns

| Export | In the mock |
| --- | --- |
| `<Scene>` | A `View` (`testID="apricot-scene"`) with its `style` and children |
| Every other component | Renders its children only. A `ref` gets a handle whose methods do nothing |
| `useFrame`, `useSceneTouches`, `useAttach`, `useTransform` | Do nothing |
| `useScene`, `useParent`, `usePath`, `useTrip`, `useWaves`, `useShore` | Objects whose properties and methods do nothing |
| `useAnimatable(value)` | Returns a reader that gives `value` |
| `useDaylight()` | Returns a reader that gives `null` |
| `useModel` | `{ object: null, animations: [], error: null }` |
| `useTexture`, `useTextureState` | `null`, `{ texture: null, error: null }` |
| `captureScene` | Resolves `{ uri: "file:///apricot/capture.png", width: 1, height: 1 }` |
| `download`, `isDownloaded`, `measureModel` | Resolve at once; `isDownloaded` is `true`; `measureModel` is a 1 m box |
| `useLayout`, `fileStorage` | The real ones |

Taps on 3D objects can't be simulated: test what your handlers do by calling them, or by pressing the React Native buttons that drive the scene.

## Related

- [Placing things by hand](https://matthiasbrandolin.dev/apricot/guides/placement#keeping-the-layout-uselayout): `useLayout`.
- [Installation](https://matthiasbrandolin.dev/apricot/installation).
