Skip to content

Commit 063c7d5

Browse files
robhoganmeta-codesync[bot]
authored andcommitted
react-native-babel-preset: Add transform snapshot tests (#55338)
Summary: Pull Request resolved: #55338 Add snapshot tests to validate Babel transform output under `react-native/babel-preset` under different configurations (default, hermes-stable, hermes-canary) and options (dev/prod, ESM output, babel-runtime). This enables catching unintended transform changes during preset updates. The tests use a "kitchen-sink" input fixture covering private fields, async generators, Flow enums, JSX, hooks, and other syntax patterns, with separate output files per configuration for easy diffing. Changelog: [Internal] Reviewed By: vzaidman Differential Revision: D91677590 fbshipit-source-id: d28becdd93a781004287dcd1889ac50224d2a91b
1 parent fdc1f3c commit 063c7d5

13 files changed

Lines changed: 3554 additions & 0 deletions

.eslintignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,4 @@ private/react-native-codegen-typescript-test/lib/**/*
1717
**/Pods/*
1818
**/*.macos.js
1919
**/*.windows.js
20+
**/__fixtures__/**
Lines changed: 261 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow strict-local
8+
* @format
9+
*/
10+
11+
import type {Node} from 'react';
12+
13+
import * as React from 'react';
14+
import {useEffect, useState} from 'react';
15+
16+
// Flow enum
17+
enum Status {
18+
Active,
19+
Inactive,
20+
Pending,
21+
}
22+
23+
// Private class fields and methods
24+
class Counter {
25+
#count: number = 0;
26+
static #instances: number = 0;
27+
28+
constructor() {
29+
Counter.#instances++;
30+
}
31+
32+
#increment(): void {
33+
this.#count++;
34+
}
35+
36+
// $FlowExpectedError[unsafe-getters-setters] - Testing getter syntax
37+
get value(): number {
38+
return this.#count;
39+
}
40+
41+
increment(): void {
42+
this.#increment();
43+
}
44+
45+
// $FlowExpectedError[unsafe-getters-setters] - Testing static getter syntax
46+
static get instanceCount(): number {
47+
return Counter.#instances;
48+
}
49+
}
50+
51+
// Async generator function
52+
async function* asyncNumberGenerator(
53+
max: number,
54+
): AsyncGenerator<number, void, void> {
55+
for (let i = 0; i < max; i++) {
56+
await new Promise(resolve => setTimeout(resolve, 100));
57+
yield i;
58+
}
59+
}
60+
61+
// Async/await patterns
62+
async function fetchData(url: string): Promise<{data: mixed}> {
63+
const response = await fetch(url);
64+
const data = await response.json();
65+
return {data};
66+
}
67+
68+
// Optional chaining and nullish coalescing
69+
function getNestedValue(obj: ?{a?: {b?: {c?: number}}}): number {
70+
return obj?.a?.b?.c ?? 42;
71+
}
72+
73+
// Class with various features
74+
class Animal {
75+
name: string;
76+
#age: number;
77+
78+
constructor(name: string, age: number) {
79+
this.name = name;
80+
this.#age = age;
81+
}
82+
83+
speak(): string {
84+
return `${this.name} makes a sound`;
85+
}
86+
87+
// $FlowExpectedError[unsafe-getters-setters] - Testing getter syntax
88+
get age(): number {
89+
return this.#age;
90+
}
91+
}
92+
93+
class Dog extends Animal {
94+
breed: string;
95+
96+
constructor(name: string, age: number, breed: string) {
97+
super(name, age);
98+
this.breed = breed;
99+
}
100+
101+
speak(): string {
102+
return `${this.name} barks!`;
103+
}
104+
105+
async fetchTreats(): Promise<Array<string>> {
106+
await new Promise(resolve => setTimeout(resolve, 100));
107+
return ['bone', 'biscuit', 'toy'];
108+
}
109+
}
110+
111+
// Destructuring patterns
112+
function processUser({
113+
name,
114+
age = 18,
115+
...rest
116+
}: {
117+
name: string,
118+
age?: number,
119+
city?: string,
120+
...
121+
}): string {
122+
const {city = 'Unknown'} = rest;
123+
return `${name} (${age}) from ${city}`;
124+
}
125+
126+
// Spread operators
127+
function mergeConfigs<T: {}>(
128+
base: T,
129+
...overrides: ReadonlyArray<Partial<T>>
130+
): T {
131+
// $FlowExpectedError[incompatible-return] - Testing spread syntax
132+
// $FlowExpectedError[incompatible-type] - Testing spread syntax
133+
return {...base, ...overrides.reduce((acc, o) => ({...acc, ...o}), {})};
134+
}
135+
136+
// for...of with destructuring
137+
function sumPairs(pairs: Array<[number, number]>): number {
138+
let total = 0;
139+
for (const [a, b] of pairs) {
140+
total += a + b;
141+
}
142+
return total;
143+
}
144+
145+
// Named capturing groups in regex
146+
function parseDate(
147+
dateString: string,
148+
): ?{year: string, month: string, day: string} {
149+
const regex = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
150+
const match = dateString.match(regex);
151+
if (match?.groups) {
152+
return {
153+
year: match.groups.year,
154+
month: match.groups.month,
155+
day: match.groups.day,
156+
};
157+
}
158+
return null;
159+
}
160+
161+
// Try-catch with optional binding
162+
function safeJsonParse(input: string): mixed {
163+
try {
164+
return JSON.parse(input);
165+
} catch {
166+
return null;
167+
}
168+
}
169+
170+
// Unicode regex
171+
function matchEmoji(text: string): ?string {
172+
const match = text.match(/\p{Emoji}/u);
173+
return match?.[0];
174+
}
175+
176+
// Class expression
177+
const MyClass = class {
178+
value: number;
179+
constructor(value: number) {
180+
this.value = value;
181+
}
182+
};
183+
184+
// Dynamic import (syntax only)
185+
async function loadModule(): Promise<mixed> {
186+
// $FlowExpectedError[cannot-resolve-module] - Testing dynamic import syntax
187+
const module = await import('./some-module');
188+
return module.default;
189+
}
190+
191+
// React component using createClass pattern (legacy)
192+
// $FlowExpectedError[prop-missing] - Testing legacy React.createClass
193+
// $FlowExpectedError[signature-verification-failure] - Testing legacy React.createClass
194+
const LegacyComponent = React.createClass({
195+
displayName: 'LegacyComponent',
196+
getInitialState() {
197+
return {count: 0};
198+
},
199+
render() {
200+
// $FlowExpectedError[object-this-reference] - Testing legacy this reference
201+
return <div>{this.state.count}</div>;
202+
},
203+
});
204+
205+
// Modern React functional component with hooks
206+
function ModernComponent({initialCount = 0}: {initialCount?: number}): Node {
207+
const [count, setCount] = useState<number>(initialCount);
208+
const [status, setStatus] = useState<Status>(Status.Active);
209+
210+
useEffect(() => {
211+
const timer = setInterval(() => {
212+
setCount(c => c + 1);
213+
}, 1000);
214+
return () => clearInterval(timer);
215+
}, []);
216+
217+
async function handleAsyncClick(): Promise<void> {
218+
const data = await fetchData('/api/data');
219+
console.log(data);
220+
}
221+
222+
const handleClick = async () => {
223+
await handleAsyncClick();
224+
setStatus(Status.Pending);
225+
};
226+
227+
return (
228+
<div>
229+
<span data-testid="count">{count}</span>
230+
<span data-testid="status">{String(status)}</span>
231+
<button onClick={handleClick}>Increment</button>
232+
<LegacyComponent />
233+
</div>
234+
);
235+
}
236+
237+
// Export default from syntax
238+
// $FlowExpectedError[cannot-resolve-module] - Testing export default from syntax
239+
export {fetchData as default} from './data-utils';
240+
241+
// Named exports
242+
export {
243+
Counter,
244+
Animal,
245+
Dog,
246+
asyncNumberGenerator,
247+
getNestedValue,
248+
processUser,
249+
mergeConfigs,
250+
sumPairs,
251+
parseDate,
252+
safeJsonParse,
253+
matchEmoji,
254+
MyClass,
255+
loadModule,
256+
ModernComponent,
257+
LegacyComponent,
258+
};
259+
260+
// Type exports
261+
export type {Node};

0 commit comments

Comments
 (0)