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
2 changes: 1 addition & 1 deletion .flowconfig
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ module.name_mapper='^react-native/src/fb_internal/\(.*\)$' -> '<PROJECT_ROOT>/pa
# `src/fb_internal/*` stay importable.
module.name_mapper='^react-native/\(Libraries\|src/private\|src/types\)/.*$' -> 'react-native-deep-import-is-not-part-of-the-public-api'
module.name_mapper='^@react-native/dev-middleware$' -> '<PROJECT_ROOT>/packages/dev-middleware'
module.name_mapper='^@?[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\|xml\|ktx\|heic\|heif\)$' -> '<PROJECT_ROOT>/packages/react-native/Libraries/Image/RelativeImageStub'
module.name_mapper='^@?[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\|xml\|ktx\|heic\|ttf\|heif\)$' -> '<PROJECT_ROOT>/packages/react-native/Libraries/Image/RelativeImageStub'

module.system.haste.module_ref_prefix=m#

Expand Down
17 changes: 17 additions & 0 deletions flow-typed/npm/pngjs_v7.x.x.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/**
* @flow strict
* @format
*/

declare module 'pngjs/lib/png' {
declare export class PNG {
constructor(options: {width: number, height: number}): void;
width: number;
height: number;
data: Buffer;
static sync: {
read(buffer: Buffer): PNG,
write(image: PNG): Buffer,
};
}
}
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
"@babel/preset-env": "^7.25.3",
"@babel/preset-flow": "^7.24.7",
"@electron/packager": "^20.0.0",
"@expo-google-fonts/noto-color-emoji": "0.4.7",
"@expo/spawn-async": "^1.7.2",
"@jest/create-cache-key-function": "^29.7.0",
"@jest/test-sequencer": "^29.7.0",
Expand All @@ -82,6 +83,7 @@
"connect": "^3.6.5",
"debug": "^4.4.0",
"deep-equal": "1.1.1",
"dejavu-fonts-ttf": "2.37.3",
"eslint": "^8.57.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-babel": "^5.3.1",
Expand Down
4 changes: 2 additions & 2 deletions packages/new-app-screen/src/NewAppScreen.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
*/

import Links from './Links';
import RuntimeVersion from './RuntimeVersion';
import {ThemedText, useTheme} from './Theme';
import * as React from 'react';
import {
Expand Down Expand Up @@ -135,8 +136,7 @@ function getHermesLabel(): React.Node {

return (
<ThemedText color="secondary" style={styles.label}>
JS Engine: Hermes (
{global.HermesInternal.getRuntimeProperties?.()['OSS Release Version']})
JS Engine: Hermes ({RuntimeVersion.hermes})
</ThemedText>
);
}
Expand Down
16 changes: 16 additions & 0 deletions packages/new-app-screen/src/RuntimeVersion.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/

const RuntimeVersion: {hermes: string | void} = {
hermes:
global.HermesInternal?.getRuntimeProperties?.()?.['OSS Release Version'],
};

export default RuntimeVersion;
4 changes: 2 additions & 2 deletions packages/react-native/Libraries/Image/ImageTypes.flow.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import type {HostInstance} from '../..';
import type {RootTag} from '../Types/RootTagTypes';
import type {ResolvedAssetSource} from './AssetSourceResolver';
import type {ImageProps as ImagePropsType} from './ImageProps';
import type {ImageSource} from './ImageSource';
import typeof resolveAssetSource from './resolveAssetSource';

import * as React from 'react';

Expand Down Expand Up @@ -59,7 +59,7 @@ type ImageComponentStaticsIOS = Readonly<{
/**
* @see https://reactnative.dev/docs/image#resolveassetsource
*/
resolveAssetSource(source: ImageSource): ?ImageResolvedAssetSource,
resolveAssetSource: resolveAssetSource,
}>;

type ImageComponentStaticsAndroid = Readonly<{
Expand Down
20 changes: 13 additions & 7 deletions packages/react-native/ReactNativeApi.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<eda44383e13efcfb4f814115c4b44547>>
* @generated SignedSource<<fcf142a69b68104edc8bfe851de32d6f>>
*
* This file was generated by scripts/js-api/build-types/index.js.
*/
Expand Down Expand Up @@ -75,6 +75,7 @@ declare const $$index: {
}
declare const $$NativeDeviceInfo: typeof NativeModule_default
declare const $$ReactFabric: typeof ReactFabric_default
declare const $$resolveAssetSource: typeof resolveAssetSource_default
declare const $$ScrollViewContext: typeof ScrollViewContext_default
declare const absoluteFill: AbsoluteFillStyle
declare const AccessibilityInfo: typeof AccessibilityInfo_default
Expand Down Expand Up @@ -917,6 +918,7 @@ declare type $$flattenStyle = typeof $$flattenStyle
declare type $$index = typeof $$index
declare type $$NativeDeviceInfo = typeof $$NativeDeviceInfo
declare type $$ReactFabric = typeof $$ReactFabric
declare type $$resolveAssetSource = typeof $$resolveAssetSource
declare type $$ScrollViewContext = typeof $$ScrollViewContext
declare type absoluteFill = typeof absoluteFill
declare type AbsoluteFillStyle = {
Expand Down Expand Up @@ -2437,6 +2439,7 @@ declare type ImageComponentStaticsAndroid = Readonly<
}
>
declare type ImageComponentStaticsIOS = {
readonly resolveAssetSource: resolveAssetSource
getSize(uri: string): Promise<ImageSize>
getSize(
uri: string,
Expand Down Expand Up @@ -2466,7 +2469,6 @@ declare type ImageComponentStaticsIOS = {
queryCache(urls: Array<string>): Promise<{
[url: string]: "disk" | "disk/memory" | "memory"
}>
resolveAssetSource(source: ImageSource): ImageResolvedAssetSource | undefined
}
declare type ImageErrorEvent = NativeSyntheticEvent<
Readonly<ImageErrorEventData>
Expand Down Expand Up @@ -4116,6 +4118,10 @@ declare type RequiredVirtualizedSectionListProps<
sections: ReadonlyArray<SectionListData<ItemT, SectionT>>
}
declare type requireNativeComponent = typeof requireNativeComponent
declare type resolveAssetSource = typeof $$resolveAssetSource
declare function resolveAssetSource_default(
source: ImageSource | null | undefined,
): null | ResolvedAssetSource | undefined
declare type ResolvedAssetSource = {
readonly height: number | undefined
readonly scale: number
Expand Down Expand Up @@ -5791,7 +5797,7 @@ export {
AlertOptions, // 8a116d2a
AlertType, // 5ab91217
AndroidKeyboardEvent, // e03becc8
Animated, // 33fe7464
Animated, // adba9272
AppConfig, // 35c0ca70
AppRegistry, // edec2a30
AppState, // 12012be5
Expand Down Expand Up @@ -5856,10 +5862,10 @@ export {
IEventEmitter, // fbef6131
IOSKeyboardEvent, // e67bfe3a
IgnorePattern, // ec6f6ece
Image, // a24a5777
ImageBackground, // 1fbbacef
ImageBackgroundInstance, // 9c900a60
ImageBackgroundProps, // b078f0ac
Image, // 404e5c9b
ImageBackground, // d2afc8de
ImageBackgroundInstance, // 2e9ed91b
ImageBackgroundProps, // d49fc98c
ImageErrorEvent, // b6227714
ImageInstance, // df709524
ImageLoadEvent, // 31709c38
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import type {
RootTag,
TurboModule,
} from '../../../../../Libraries/TurboModule/RCTExport';
import type {UnsafeObject} from '../../../../../Libraries/Types/CodegenTypes';

import * as TurboModuleRegistry from '../../../../../Libraries/TurboModule/TurboModuleRegistry';

Expand Down Expand Up @@ -116,6 +117,9 @@ interface Spec extends TurboModule {
surfaceId: RootTag,
config: RenderFormatOptions,
) => string;
takeScreenshot(surfaceId: RootTag): UnsafeObject;
registerAsset(uri: string, filePath: string): void;
registerFont(filePath: string): void;
reportTestSuiteResultsJSON: (results: string) => void;
createShadowNodeReferenceCounter(
shadowNode: unknown /* ShadowNode */,
Expand Down
57 changes: 57 additions & 0 deletions private/react-native-fantom/__docs__/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,11 @@ values. If another root is still alive and the new metrics differ from the
current global dimensions, Fantom applies the new values and emits a warning.
Call `root.destroy()` when a root is no longer needed.

Each root also uses its `fontScale` for native text and text-input measurement,
including line heights and wrapping. `allowFontScaling={false}` opts a component
out, and `maxFontSizeMultiplier` caps its scale. Existing roots retain their own
native font scale when a new root changes the global display metrics.

For a full API reference, please see the [inline documentation](../src/index.js)
defined for the methods in the `@react-native/fantom` [module](../src/index.js).

Expand All @@ -119,6 +124,49 @@ Similar to Jest, you can also run Fantom in watch mode using `--watch`:
yarn fantom <regexForTestFiles> --watch
```

### PNG screenshots

`root.takeScreenshot()` captures the currently mounted Fabric revision into an
immutable `Screenshot`. Capture after `runTask` to include its mounted updates;
capture itself does not flush pending React work. The bitmap remains usable
after later updates or root destruction.

```javascript
const root = Fantom.createRoot();
Fantom.runTask(() => root.render(<Screen />));
const screenshot = root.takeScreenshot();
expect(screenshot).toMatchSnapshot();
expect(screenshot.isEqual(root.takeScreenshot())).toBe(true);
```

The headless renderer paints Yoga's mounted frames with CPU Skia. Text uses the
same layout and bundled fonts as intrinsic measurement, so the measured sizes
and painted content agree. `isEqual()` compares bitmap dimensions and pixels in
memory.

`toMatchSnapshot()` compares a SHA-256 hash of the bitmap dimensions and pixels
with the value in Jest's `.snap` file. The expected PNG is not read for
comparison. The runner also keeps an inspectable image in
`__snapshots__/<test-file>.screenshots/`, named from the test, label and
assertion number. Capture defers PNG encoding until an image must be saved;
encoded bytes are cached for repeated saves. A matching hash passes even if the
PNG is missing, and the runner regenerates that image.

`toMatchInlineSnapshot()` embeds the screenshot's type and hash directly in the
test source. It does not encode or save an image, so inline failures report a
text diff.

New snapshots are saved during a normal local run. `--updateSnapshot` (`-u`)
accepts changed hashes and PNGs together; CI requires an existing matching hash.
A mismatch leaves the baseline unchanged, saves a difference image alongside the
received image, and reports all three image paths for inspection. Unused images
are removed only during a successful snapshot update with no skipped or filtered
tests. Negated snapshot assertions are unsupported.

See [Rendering](rendering.md) for the painting architecture, supported features,
font selection and the configuration that keeps shared baselines consistent
across Linux and macOS.

### Interactive REPL

> [!WARNING]
Expand Down Expand Up @@ -714,3 +762,12 @@ The runner then follows these steps:
[React Native Feature Flags](../../../packages/react-native/src/private/featureflags/__docs__/README.md)
system, via the `@fantom_flags` pragmas defined in the docblock for test
files.

## Text measurement

Fantom always measures intrinsic `Text` and `TextInput` sizes so layout tests
exercise wrapping and font scaling without extra setup. Measurement and painting
share Skia and bundled fonts to give tests the same layout on every host.

See [Rendering](rendering.md) for font selection, font scaling, native build
dependencies and cross-platform raster configuration.
Loading
Loading