main
ts 430 lines 9.23 KB
Raw
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
8 import {IntlVariations, IntlViewerContext, init} from 'fbt';
9 import React, {FunctionComponent} from 'react';
10
11 /**
12 * This file is meant for use by `runner-evaluator` and fixture tests.
13 *
14 * Any fixture test can import constants or functions exported here.
15 * However, the import path must be the relative path from `runner-evaluator`
16 * (which calls `eval` on each fixture) to this file.
17 *
18 * ```js
19 * // test.js
20 * import {CONST_STRING0} from './shared-runtime';
21 *
22 * // ...
23 * ```
24 */
25
26 export type StringKeyedObject = {[key: string]: unknown};
27
28 export const CONST_STRING0 = 'global string 0';
29 export const CONST_STRING1 = 'global string 1';
30 export const CONST_STRING2 = 'global string 2';
31
32 export const CONST_NUMBER0 = 0;
33 export const CONST_NUMBER1 = 1;
34 export const CONST_NUMBER2 = 2;
35
36 export const CONST_TRUE = true;
37 export const CONST_FALSE = false;
38
39 export function initFbt(): void {
40 const viewerContext: IntlViewerContext = {
41 GENDER: IntlVariations.GENDER_UNKNOWN,
42 locale: 'en_US',
43 };
44
45 init({
46 translations: {},
47 hooks: {
48 getViewerContext: () => viewerContext,
49 },
50 });
51 }
52
53 export function mutate(arg: any): void {
54 // don't mutate primitive
55 if (arg == null || typeof arg !== 'object') {
56 return;
57 } else if (Array.isArray(arg)) {
58 arg.push('joe');
59 }
60
61 let count: number = 0;
62 let key;
63 while (true) {
64 key = 'wat' + count;
65 if (!Object.hasOwn(arg, key)) {
66 arg[key] = 'joe';
67 return;
68 }
69 count++;
70 }
71 }
72
73 export function mutateAndReturn<T>(arg: T): T {
74 mutate(arg);
75 return arg;
76 }
77
78 export function mutateAndReturnNewValue<T>(arg: T): string {
79 mutate(arg);
80 return 'hello!';
81 }
82
83 export function setProperty(arg: any, property: any): void {
84 // don't mutate primitive
85 if (arg == null || typeof arg !== 'object') {
86 return arg;
87 }
88
89 let count: number = 0;
90 let key;
91 while (true) {
92 key = 'wat' + count;
93 if (!Object.hasOwn(arg, key)) {
94 arg[key] = property;
95 return arg;
96 }
97 count++;
98 }
99 }
100
101 export function setPropertyByKey<
102 T,
103 TKey extends keyof T,
104 TProperty extends T[TKey],
105 >(arg: T, key: TKey, property: TProperty): T {
106 arg[key] = property;
107 return arg;
108 }
109
110 export function arrayPush<T>(arr: Array<T>, ...values: Array<T>): Array<T> {
111 arr.push(...values);
112 return arr;
113 }
114
115 export function graphql(value: string): string {
116 return value;
117 }
118
119 export function identity<T>(x: T): T {
120 return x;
121 }
122
123 export function getNumber(): number {
124 return 4;
125 }
126
127 export function getNull(): null {
128 return null;
129 }
130
131 export function getTrue(): true {
132 return true;
133 }
134
135 export function getFalse(): false {
136 return false;
137 }
138
139 export function calculateExpensiveNumber(x: number): number {
140 return x;
141 }
142
143 /**
144 * Functions that do not mutate their parameters
145 */
146 export function shallowCopy<T extends object>(obj: T): T {
147 return Object.assign({}, obj);
148 }
149
150 export function makeObject_Primitives(): StringKeyedObject {
151 return {a: 0, b: 'value1', c: true};
152 }
153
154 export function makeArray<T>(...values: Array<T>): Array<T> {
155 return [...values];
156 }
157
158 export function addOne(value: number): number {
159 return value + 1;
160 }
161
162 /*
163 * Alias console.log, as it is defined as a global and may have
164 * different compiler handling than unknown functions
165 */
166 export function print(...args: Array<unknown>): void {
167 console.log(...args);
168 }
169
170 export function sum(...args: Array<number>): number {
171 return args.reduce((result, arg) => result + arg, 0);
172 }
173
174 export function throwErrorWithMessage(message: string): never {
175 throw new Error(message);
176 }
177
178 export function throwInput(x: object): never {
179 throw x;
180 }
181
182 export function throwErrorWithMessageIf(cond: boolean, message: string): void {
183 if (cond) {
184 throw new Error(message);
185 }
186 }
187
188 export function logValue<T>(value: T): void {
189 console.log(value);
190 }
191
192 export function useHook(): object {
193 return makeObject_Primitives();
194 }
195
196 const noAliasObject = Object.freeze({});
197 export function useNoAlias(..._args: Array<any>): object {
198 return noAliasObject;
199 }
200
201 export function useIdentity<T>(arg: T): T {
202 return arg;
203 }
204
205 export function invoke<T extends Array<any>, ReturnType>(
206 fn: (...input: T) => ReturnType,
207 ...params: T
208 ): ReturnType {
209 return fn(...params);
210 }
211
212 export function conditionalInvoke<T extends Array<any>, ReturnType>(
213 shouldInvoke: boolean,
214 fn: (...input: T) => ReturnType,
215 ...params: T
216 ): ReturnType | null {
217 if (shouldInvoke) {
218 return fn(...params);
219 } else {
220 return null;
221 }
222 }
223
224 /**
225 * React Components
226 */
227 export function Text(props: {
228 value: string;
229 children?: Array<React.ReactNode>;
230 }): React.ReactElement {
231 return React.createElement('div', null, props.value, props.children);
232 }
233
234 export function StaticText1(props: {
235 children?: Array<React.ReactNode>;
236 }): React.ReactElement {
237 return React.createElement('div', null, 'StaticText1', props.children);
238 }
239
240 export function StaticText2(props: {
241 children?: Array<React.ReactNode>;
242 }): React.ReactElement {
243 return React.createElement('div', null, 'StaticText2', props.children);
244 }
245
246 export function RenderPropAsChild(props: {
247 items: Array<() => React.ReactNode>;
248 }): React.ReactElement {
249 return React.createElement(
250 'div',
251 null,
252 'HigherOrderComponent',
253 props.items.map(item => item()),
254 );
255 }
256
257 export function Stringify(props: any): React.ReactElement {
258 return React.createElement(
259 'div',
260 null,
261 toJSON(props, props?.shouldInvokeFns),
262 );
263 }
264 export function Throw() {
265 throw new Error();
266 }
267
268 export function ValidateMemoization({
269 inputs,
270 output: rawOutput,
271 onlyCheckCompiled = false,
272 }: {
273 inputs: Array<any>;
274 output: any;
275 onlyCheckCompiled?: boolean;
276 }): React.ReactElement {
277 'use no forget';
278 // Wrap rawOutput as it might be a function, which useState would invoke.
279 const output = {value: rawOutput};
280 const [previousInputs, setPreviousInputs] = React.useState(inputs);
281 const [previousOutput, setPreviousOutput] = React.useState(output);
282 if (
283 !onlyCheckCompiled ||
284 (onlyCheckCompiled &&
285 (globalThis as any).__SNAP_EVALUATOR_MODE === 'forget')
286 ) {
287 if (
288 inputs.length !== previousInputs.length ||
289 inputs.some((item, i) => item !== previousInputs[i])
290 ) {
291 // Some input changed, we expect the output to change
292 setPreviousInputs(inputs);
293 setPreviousOutput(output);
294 } else if (output.value !== previousOutput.value) {
295 // Else output should be stable
296 throw new Error('Output identity changed but inputs did not');
297 }
298 }
299 return React.createElement(Stringify, {inputs, output: rawOutput});
300 }
301
302 export function createHookWrapper<TProps, TRet>(
303 useMaybeHook: (props: TProps) => TRet,
304 ): FunctionComponent<TProps> {
305 return function Component(props: TProps): React.ReactElement {
306 const result = useMaybeHook(props);
307 return Stringify({
308 result: result,
309 shouldInvokeFns: true,
310 });
311 };
312 }
313
314 // helper functions
315 export function toJSON(value: any, invokeFns: boolean = false): string {
316 const seen = new Map();
317
318 return JSON.stringify(value, (_key: string, val: any) => {
319 if (typeof val === 'function') {
320 if (val.length === 0 && invokeFns) {
321 return {
322 kind: 'Function',
323 result: val(),
324 };
325 } else {
326 return `[[ function params=${val.length} ]]`;
327 }
328 } else if (typeof val === 'object') {
329 let id = seen.get(val);
330 if (id != null) {
331 return `[[ cyclic ref *${id} ]]`;
332 } else if (val instanceof Map) {
333 return {
334 kind: 'Map',
335 value: Array.from(val.entries()),
336 };
337 } else if (val instanceof Set) {
338 return {
339 kind: 'Set',
340 value: Array.from(val.values()),
341 };
342 }
343 seen.set(val, seen.size);
344 }
345 return val;
346 });
347 }
348 export class Builder {
349 vals: Array<any> = [];
350 static makeBuilder(isNull: boolean, ...args: Array<any>): Builder | null {
351 if (isNull) {
352 return null;
353 } else {
354 const builder = new Builder();
355 builder.push(...args);
356 return builder;
357 }
358 }
359 push(...args: Array<any>): Builder {
360 this.vals.push(...args);
361 return this;
362 }
363 }
364
365 export const ObjectWithHooks = {
366 useFoo(): number {
367 return 0;
368 },
369 useMakeArray(): Array<number> {
370 return [1, 2, 3];
371 },
372 useIdentity<T>(arg: T): T {
373 return arg;
374 },
375 };
376
377 export function useFragment(..._args: Array<any>): object {
378 return {
379 a: [1, 2, 3],
380 b: {c: {d: 4}},
381 };
382 }
383
384 export function useSpecialEffect(
385 fn: () => any,
386 _secondArg: any,
387 deps: Array<any>,
388 ) {
389 React.useEffect(fn, deps);
390 }
391
392 export function typedArrayPush<T>(array: Array<T>, item: T): void {
393 array.push(item);
394 }
395
396 export function typedLog(...values: Array<any>): void {
397 console.log(...values);
398 }
399
400 export function typedIdentity<T>(value: T): T {
401 return value;
402 }
403
404 export function typedAssign<T>(x: T): T {
405 return x;
406 }
407
408 export function typedAlias<T>(x: T): T {
409 return x;
410 }
411
412 export function typedCapture<T>(x: T): Array<T> {
413 return [x];
414 }
415
416 export function typedCreateFrom<T>(array: Array<T>): T {
417 return array[0];
418 }
419
420 export function typedMutate(x: any, v: any = null): void {
421 x.property = v;
422 }
423
424 export const PanResponder = {
425 create(obj: any): any {
426 return obj;
427 },
428 };
429
430 export default typedLog;