Skip to content
Closed
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 packages/react-native-fantom/runner/getFantomTestConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ const DEFAULT_MODE: FantomTestConfigMode =
const FANTOM_FLAG_FORMAT = /^(\w+):(\w+)$/;

const FANTOM_BENCHMARK_FILENAME_RE = /[Bb]enchmark-itest\./g;
const FANTOM_BENCHMARK_SUITE_RE = /\nunstable_benchmark(\s*)\.suite\(/g;
const FANTOM_BENCHMARK_SUITE_RE = /\nFantom.unstable_benchmark(\s*)\.suite\(/g;

/**
* Extracts the Fantom configuration from the test file, specified as part of
Expand Down
96 changes: 45 additions & 51 deletions packages/react-native-fantom/src/__tests__/Fantom-itest.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,7 @@ import 'react-native/Libraries/Core/InitializeCore';

import type {Root} from '..';

import {
createRoot,
dispatchNativeEvent,
runOnUIThread,
runTask,
runWorkLoop,
} from '..';
import Fantom from '..';
import * as React from 'react';
import {ScrollView, Text, TextInput, View} from 'react-native';
import ensureInstance from 'react-native/src/private/utilities/ensureInstance';
Expand All @@ -32,7 +26,7 @@ function getActualViewportDimensions(root: Root): {
} {
let maybeNode;

runTask(() => {
Fantom.runTask(() => {
root.render(
<View
style={{width: '100%', height: '100%'}}
Expand All @@ -57,7 +51,7 @@ describe('Fantom', () => {
it('should run a task synchronously', () => {
const task = jest.fn();

runTask(task);
Fantom.runTask(task);

expect(task).toHaveBeenCalledTimes(1);
});
Expand All @@ -66,7 +60,7 @@ describe('Fantom', () => {
// eslint-disable-next-line jest/no-disabled-tests
it.skip('should re-throw errors from the task synchronously', () => {
expect(() => {
runTask(() => {
Fantom.runTask(() => {
throw new Error('test error');
});
}).toThrow('test error');
Expand All @@ -75,7 +69,7 @@ describe('Fantom', () => {
it('should exhaust the microtask queue synchronously', () => {
const lastMicrotask = jest.fn();

runTask(() => {
Fantom.runTask(() => {
queueMicrotask(() => {
queueMicrotask(() => {
queueMicrotask(() => {
Expand All @@ -92,7 +86,7 @@ describe('Fantom', () => {
// eslint-disable-next-line jest/no-disabled-tests
it.skip('should re-throw errors from microtasks synchronously', () => {
expect(() => {
runTask(() => {
Fantom.runTask(() => {
queueMicrotask(() => {
throw new Error('test error');
});
Expand All @@ -103,7 +97,7 @@ describe('Fantom', () => {
it('should run async tasks synchronously', () => {
let completed = false;

runTask(async () => {
Fantom.runTask(async () => {
await Promise.resolve(6);
completed = true;
});
Expand All @@ -115,10 +109,10 @@ describe('Fantom', () => {
it('should throw when running a task inside another task', () => {
let threw = false;

runTask(() => {
Fantom.runTask(() => {
// TODO replace with expect(() => { ... }).toThrow() when error handling is fixed
try {
runTask(() => {});
Fantom.runTask(() => {});
} catch {
threw = true;
}
Expand All @@ -127,10 +121,10 @@ describe('Fantom', () => {

threw = false;

runTask(() => {
Fantom.runTask(() => {
queueMicrotask(() => {
try {
runTask(() => {});
Fantom.runTask(() => {});
} catch {
threw = true;
}
Expand All @@ -142,14 +136,14 @@ describe('Fantom', () => {

describe('createRoot', () => {
it('allows creating a root with specific dimensions', () => {
const rootWithDefaults = createRoot();
const rootWithDefaults = Fantom.createRoot();

expect(getActualViewportDimensions(rootWithDefaults)).toEqual({
viewportWidth: 390,
viewportHeight: 844,
});

const rootWithCustomWidthAndHeight = createRoot({
const rootWithCustomWidthAndHeight = Fantom.createRoot({
viewportWidth: 200,
viewportHeight: 600,
});
Expand All @@ -166,9 +160,9 @@ describe('Fantom', () => {
describe('getRenderedOutput', () => {
describe('toJSX', () => {
it('default config', () => {
const root = createRoot();
const root = Fantom.createRoot();

runTask(() => {
Fantom.runTask(() => {
root.render(
<View style={{width: 100, height: 100}} collapsable={false} />,
);
Expand All @@ -182,9 +176,9 @@ describe('Fantom', () => {
});

it('default config, list of children', () => {
const root = createRoot();
const root = Fantom.createRoot();

runTask(() => {
Fantom.runTask(() => {
root.render(
<>
<View
Expand Down Expand Up @@ -212,9 +206,9 @@ describe('Fantom', () => {
});

it('include root', () => {
const root = createRoot();
const root = Fantom.createRoot();

runTask(() => {
Fantom.runTask(() => {
root.render(
<View style={{width: 100, height: 100}} collapsable={false} />,
);
Expand All @@ -230,9 +224,9 @@ describe('Fantom', () => {
});

it('include layout metrics', () => {
const root = createRoot();
const root = Fantom.createRoot();

runTask(() => {
Fantom.runTask(() => {
root.render(
<View style={{width: 100, height: 100}} collapsable={false} />,
);
Expand All @@ -258,9 +252,9 @@ describe('Fantom', () => {
});

it('take props', () => {
const root = createRoot();
const root = Fantom.createRoot();

runTask(() => {
Fantom.runTask(() => {
root.render(
<View style={{width: 100, height: 100}} collapsable={false} />,
);
Expand All @@ -278,9 +272,9 @@ describe('Fantom', () => {
});

it('skip props', () => {
const root = createRoot();
const root = Fantom.createRoot();

runTask(() => {
Fantom.runTask(() => {
root.render(
<View style={{width: 100, height: 100}} collapsable={false} />,
);
Expand All @@ -298,9 +292,9 @@ describe('Fantom', () => {
});

it('filter out all props', () => {
const root = createRoot();
const root = Fantom.createRoot();

runTask(() => {
Fantom.runTask(() => {
root.render(
<>
<View
Expand Down Expand Up @@ -332,9 +326,9 @@ describe('Fantom', () => {

describe('toJSON', () => {
it('nested text', () => {
const root = createRoot();
const root = Fantom.createRoot();

runTask(() => {
Fantom.runTask(() => {
root.render(
<Text>
Testing native{' '}
Expand Down Expand Up @@ -378,12 +372,12 @@ describe('Fantom', () => {

describe('runOnUIThread + dispatchNativeEvent', () => {
it('sends event without payload', () => {
const root = createRoot();
const root = Fantom.createRoot();
let maybeNode;

let focusEvent = jest.fn();

runTask(() => {
Fantom.runTask(() => {
root.render(
<TextInput
onFocus={focusEvent}
Expand All @@ -398,25 +392,25 @@ describe('Fantom', () => {

expect(focusEvent).toHaveBeenCalledTimes(0);

runOnUIThread(() => {
dispatchNativeEvent(element, 'focus');
Fantom.runOnUIThread(() => {
Fantom.dispatchNativeEvent(element, 'focus');
});

// The tasks have not run.
expect(focusEvent).toHaveBeenCalledTimes(0);

runWorkLoop();
Fantom.runWorkLoop();

expect(focusEvent).toHaveBeenCalledTimes(1);
});
});

it('sends event with payload', () => {
const root = createRoot();
const root = Fantom.createRoot();
let maybeNode;
const onChange = jest.fn();

runTask(() => {
Fantom.runTask(() => {
root.render(
<TextInput
onChange={event => {
Expand All @@ -431,25 +425,25 @@ describe('Fantom', () => {

const element = ensureInstance(maybeNode, ReactNativeElement);

runOnUIThread(() => {
dispatchNativeEvent(element, 'change', {
Fantom.runOnUIThread(() => {
Fantom.dispatchNativeEvent(element, 'change', {
text: 'Hello World',
});
});

runWorkLoop();
Fantom.runWorkLoop();

expect(onChange).toHaveBeenCalledTimes(1);
const [entry] = onChange.mock.lastCall;
expect(entry.text).toEqual('Hello World');
});

it('it batches events with isUnique option', () => {
const root = createRoot();
const root = Fantom.createRoot();
let maybeNode;
const onScroll = jest.fn();

runTask(() => {
Fantom.runTask(() => {
root.render(
<ScrollView
onScroll={event => {
Expand All @@ -464,14 +458,14 @@ describe('Fantom', () => {

const element = ensureInstance(maybeNode, ReactNativeElement);

runOnUIThread(() => {
dispatchNativeEvent(element, 'scroll', {
Fantom.runOnUIThread(() => {
Fantom.dispatchNativeEvent(element, 'scroll', {
contentOffset: {
x: 0,
y: 1,
},
});
dispatchNativeEvent(
Fantom.dispatchNativeEvent(
element,
'scroll',
{
Expand All @@ -486,7 +480,7 @@ describe('Fantom', () => {
);
});

runWorkLoop();
Fantom.runWorkLoop();

expect(onScroll).toHaveBeenCalledTimes(1);
const [entry] = onScroll.mock.lastCall;
Expand Down
24 changes: 16 additions & 8 deletions packages/react-native-fantom/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ const DEFAULT_TASK_PRIORITY = schedulerPriorityImmediate;
* If the work loop is running, it will be executed according to its priority.
* Otherwise, it will wait in the queue until the work loop runs.
*/
export function scheduleTask(task: () => void | Promise<void>) {
function scheduleTask(task: () => void | Promise<void>) {
nativeRuntimeScheduler.unstable_scheduleCallback(DEFAULT_TASK_PRIORITY, task);
}

Expand All @@ -108,7 +108,7 @@ let flushingQueue = false;
*
* React must run inside of event loop to ensure scheduling environment is closer to production.
*/
export function runTask(task: () => void | Promise<void>) {
function runTask(task: () => void | Promise<void>) {
if (flushingQueue) {
throw new Error(
'Nested `runTask` calls are not allowed. If you want to schedule a task from inside another task, use `scheduleTask` instead.',
Expand All @@ -122,15 +122,15 @@ export function runTask(task: () => void | Promise<void>) {
/*
* Simmulates running a task on the UI thread and forces side effect to drain the event queue, dispatching events to JavaScript.
*/
export function runOnUIThread(task: () => void) {
function runOnUIThread(task: () => void) {
task();
NativeFantom.flushEventQueue();
}

/**
* Runs the event loop until all tasks are executed.
*/
export function runWorkLoop(): void {
function runWorkLoop(): void {
if (flushingQueue) {
throw new Error(
'Cannot start the work loop because it is already running. If you want to schedule a task from inside another task, use `scheduleTask` instead.',
Expand All @@ -147,11 +147,11 @@ export function runWorkLoop(): void {

// TODO: Add option to define surface props and pass it to startSurface
// Surfacep rops: concurrentRoot, surfaceWidth, surfaceHeight, layoutDirection, pointScaleFactor.
export function createRoot(rootConfig?: RootConfig): Root {
function createRoot(rootConfig?: RootConfig): Root {
return new Root(rootConfig);
}

export function dispatchNativeEvent(
function dispatchNativeEvent(
node: ReactNativeElement,
type: string,
payload?: {[key: string]: mixed},
Expand All @@ -167,8 +167,6 @@ export function dispatchNativeEvent(
);
}

export const unstable_benchmark = Benchmark;

type FantomConstants = $ReadOnly<{
isRunningFromCI: boolean,
}>;
Expand Down Expand Up @@ -248,3 +246,13 @@ if (typeof global.EventTarget === 'undefined') {
'The global Event class is already defined. If this API is already defined by React Native, you might want to remove this logic.',
);
}

export default {
scheduleTask,
runTask,
runOnUIThread,
runWorkLoop,
createRoot,
dispatchNativeEvent,
unstable_benchmark: Benchmark,
};
Loading