mirror of
https://github.com/zoriya/react-native-web.git
synced 2026-05-12 19:06:35 +00:00
committed by
Nicolas Gallagher
parent
618660484f
commit
47d77ac256
@@ -95,11 +95,8 @@ const { StyleSheet, Pressable } = require('react-native');
|
||||
↓ ↓ ↓ ↓ ↓ ↓
|
||||
|
||||
const ReactNative = require('react-native-web/dist/index');
|
||||
|
||||
const View = require('react-native-web/dist/exports/View').default;
|
||||
|
||||
const StyleSheet = require('react-native-web/dist/exports/StyleSheet').default;
|
||||
|
||||
const Pressable = require('react-native-web/dist/exports/Pressable').default;
|
||||
|
||||
|
||||
@@ -114,12 +111,9 @@ const { StyleSheet, Pressable } = require('react-native');
|
||||
↓ ↓ ↓ ↓ ↓ ↓
|
||||
|
||||
const ReactNative = require('react-native-web/dist/cjs/index');
|
||||
|
||||
const View = require('react-native-web/dist/cjs/exports/View').default;
|
||||
|
||||
const StyleSheet =
|
||||
require('react-native-web/dist/cjs/exports/StyleSheet').default;
|
||||
|
||||
const Pressable =
|
||||
require('react-native-web/dist/cjs/exports/Pressable').default;
|
||||
|
||||
@@ -135,16 +129,11 @@ const { StyleSheet, View, Pressable, processColor } = require('react-native-web'
|
||||
↓ ↓ ↓ ↓ ↓ ↓
|
||||
|
||||
const ReactNative = require('react-native-web/dist/index');
|
||||
|
||||
const unstable_createElement =
|
||||
require('react-native-web/dist/exports/createElement').default;
|
||||
|
||||
const StyleSheet = require('react-native-web/dist/exports/StyleSheet').default;
|
||||
|
||||
const View = require('react-native-web/dist/exports/View').default;
|
||||
|
||||
const Pressable = require('react-native-web/dist/exports/Pressable').default;
|
||||
|
||||
const processColor =
|
||||
require('react-native-web/dist/exports/processColor').default;
|
||||
|
||||
|
||||
115
packages/react-native-web/src/exports/InteractionManager/TaskQueue.js
vendored
Normal file
115
packages/react-native-web/src/exports/InteractionManager/TaskQueue.js
vendored
Normal file
@@ -0,0 +1,115 @@
|
||||
/**
|
||||
* Copyright (c) Nicolas Gallagher.
|
||||
* 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
|
||||
*/
|
||||
|
||||
import invariant from 'fbjs/lib/invariant';
|
||||
|
||||
type SimpleTask = {|
|
||||
name: string,
|
||||
run: () => void
|
||||
|};
|
||||
type PromiseTask = {|
|
||||
name: string,
|
||||
gen: () => Promise<void>
|
||||
|};
|
||||
export type Task = SimpleTask | PromiseTask | (() => void);
|
||||
|
||||
class TaskQueue {
|
||||
constructor({ onMoreTasks }: { onMoreTasks: () => void, ... }) {
|
||||
this._onMoreTasks = onMoreTasks;
|
||||
this._queueStack = [{ tasks: [], popable: true }];
|
||||
}
|
||||
|
||||
enqueue(task: Task): void {
|
||||
this._getCurrentQueue().push(task);
|
||||
}
|
||||
|
||||
enqueueTasks(tasks: Array<Task>): void {
|
||||
tasks.forEach((task) => this.enqueue(task));
|
||||
}
|
||||
|
||||
cancelTasks(tasksToCancel: Array<Task>): void {
|
||||
this._queueStack = this._queueStack
|
||||
.map((queue) => ({
|
||||
...queue,
|
||||
tasks: queue.tasks.filter((task) => tasksToCancel.indexOf(task) === -1)
|
||||
}))
|
||||
.filter((queue, idx) => queue.tasks.length > 0 || idx === 0);
|
||||
}
|
||||
|
||||
hasTasksToProcess(): boolean {
|
||||
return this._getCurrentQueue().length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the next task in the queue.
|
||||
*/
|
||||
processNext(): void {
|
||||
const queue = this._getCurrentQueue();
|
||||
if (queue.length) {
|
||||
const task = queue.shift();
|
||||
try {
|
||||
if (typeof task === 'object' && task.gen) {
|
||||
this._genPromise(task);
|
||||
} else if (typeof task === 'object' && task.run) {
|
||||
task.run();
|
||||
} else {
|
||||
invariant(
|
||||
typeof task === 'function',
|
||||
'Expected Function, SimpleTask, or PromiseTask, but got:\n' +
|
||||
JSON.stringify(task, null, 2)
|
||||
);
|
||||
task();
|
||||
}
|
||||
} catch (e) {
|
||||
e.message =
|
||||
'TaskQueue: Error with task ' + (task.name || '') + ': ' + e.message;
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_queueStack: Array<{
|
||||
tasks: Array<Task>,
|
||||
popable: boolean,
|
||||
...
|
||||
}>;
|
||||
_onMoreTasks: () => void;
|
||||
|
||||
_getCurrentQueue(): Array<Task> {
|
||||
const stackIdx = this._queueStack.length - 1;
|
||||
const queue = this._queueStack[stackIdx];
|
||||
if (queue.popable && queue.tasks.length === 0 && stackIdx > 0) {
|
||||
this._queueStack.pop();
|
||||
return this._getCurrentQueue();
|
||||
} else {
|
||||
return queue.tasks;
|
||||
}
|
||||
}
|
||||
|
||||
_genPromise(task: PromiseTask) {
|
||||
const length = this._queueStack.push({ tasks: [], popable: false });
|
||||
const stackIdx = length - 1;
|
||||
const stackItem = this._queueStack[stackIdx];
|
||||
task
|
||||
.gen()
|
||||
.then(() => {
|
||||
stackItem.popable = true;
|
||||
this.hasTasksToProcess() && this._onMoreTasks();
|
||||
})
|
||||
.catch((ex) => {
|
||||
setTimeout(() => {
|
||||
ex.message = `TaskQueue: Error resolving Promise in task ${task.name}: ${ex.message}`;
|
||||
throw ex;
|
||||
}, 0);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default TaskQueue;
|
||||
185
packages/react-native-web/src/exports/InteractionManager/__tests__/TaskQueue-test.js
vendored
Normal file
185
packages/react-native-web/src/exports/InteractionManager/__tests__/TaskQueue-test.js
vendored
Normal file
@@ -0,0 +1,185 @@
|
||||
/**
|
||||
* Copyright (c) Nicolas Gallagher.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
function expectToBeCalledOnce(fn) {
|
||||
expect(fn.mock.calls.length).toBe(1);
|
||||
}
|
||||
|
||||
function clearTaskQueue(taskQueue) {
|
||||
do {
|
||||
jest.runAllTimers();
|
||||
taskQueue.processNext();
|
||||
jest.runAllTimers();
|
||||
} while (taskQueue.hasTasksToProcess());
|
||||
}
|
||||
|
||||
describe('TaskQueue', () => {
|
||||
let taskQueue;
|
||||
let onMoreTasks;
|
||||
let sequenceId;
|
||||
|
||||
function createSequenceTask(expectedSequenceId) {
|
||||
return jest.fn(() => {
|
||||
expect(++sequenceId).toBe(expectedSequenceId);
|
||||
});
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
onMoreTasks = jest.fn();
|
||||
const TaskQueue = require('../TaskQueue');
|
||||
taskQueue = new TaskQueue({ onMoreTasks });
|
||||
sequenceId = 0;
|
||||
});
|
||||
|
||||
it('should run a basic task', () => {
|
||||
const task1 = createSequenceTask(1);
|
||||
taskQueue.enqueue({ run: task1, name: 'run1' });
|
||||
expect(taskQueue.hasTasksToProcess()).toBe(true);
|
||||
taskQueue.processNext();
|
||||
expectToBeCalledOnce(task1);
|
||||
});
|
||||
|
||||
it('should handle blocking promise task', () => {
|
||||
onMoreTasks.mockImplementation(() => {
|
||||
taskQueue.processNext();
|
||||
jest.runAllTimers();
|
||||
});
|
||||
|
||||
const task1 = jest.fn(() => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
expect(++sequenceId).toBe(1);
|
||||
resolve();
|
||||
}, 1);
|
||||
});
|
||||
});
|
||||
const task2 = createSequenceTask(2);
|
||||
taskQueue.enqueue({ gen: task1, name: 'gen1' });
|
||||
taskQueue.enqueue({ run: task2, name: 'run2' });
|
||||
|
||||
taskQueue.processNext();
|
||||
|
||||
expectToBeCalledOnce(task1);
|
||||
expect(task2).not.toBeCalled();
|
||||
expect(onMoreTasks).not.toBeCalled();
|
||||
expect(taskQueue.hasTasksToProcess()).toBe(false);
|
||||
|
||||
clearTaskQueue(taskQueue);
|
||||
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve();
|
||||
});
|
||||
}).then(() => {
|
||||
expectToBeCalledOnce(onMoreTasks);
|
||||
expectToBeCalledOnce(task2);
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle nested simple tasks', () => {
|
||||
const task1 = jest.fn(() => {
|
||||
expect(++sequenceId).toBe(1);
|
||||
taskQueue.enqueue({ run: task3, name: 'run3' });
|
||||
});
|
||||
const task2 = createSequenceTask(2);
|
||||
const task3 = createSequenceTask(3);
|
||||
taskQueue.enqueue({ run: task1, name: 'run1' });
|
||||
taskQueue.enqueue({ run: task2, name: 'run2' }); // not blocked by task 1
|
||||
|
||||
clearTaskQueue(taskQueue);
|
||||
|
||||
expectToBeCalledOnce(task1);
|
||||
expectToBeCalledOnce(task2);
|
||||
expectToBeCalledOnce(task3);
|
||||
});
|
||||
|
||||
it('should handle nested promises', () => {
|
||||
onMoreTasks.mockImplementation(() => {
|
||||
taskQueue.processNext();
|
||||
jest.runAllTimers();
|
||||
});
|
||||
|
||||
const task1 = jest.fn(() => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
expect(++sequenceId).toBe(1);
|
||||
taskQueue.enqueue({ gen: task2, name: 'gen2' });
|
||||
taskQueue.enqueue({ run: resolve, name: 'resolve1' });
|
||||
}, 1);
|
||||
});
|
||||
});
|
||||
const task2 = jest.fn(() => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
expect(++sequenceId).toBe(2);
|
||||
taskQueue.enqueue({ run: task3, name: 'run3' });
|
||||
taskQueue.enqueue({ run: resolve, name: 'resolve2' });
|
||||
}, 1);
|
||||
});
|
||||
});
|
||||
const task3 = createSequenceTask(3);
|
||||
const task4 = createSequenceTask(4);
|
||||
taskQueue.enqueue({ gen: task1, name: 'gen1' });
|
||||
taskQueue.enqueue({ run: task4, name: 'run4' }); // blocked by task 1 promise
|
||||
|
||||
clearTaskQueue(taskQueue);
|
||||
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve();
|
||||
});
|
||||
}).then(() => {
|
||||
expectToBeCalledOnce(task1);
|
||||
expectToBeCalledOnce(task2);
|
||||
expectToBeCalledOnce(task3);
|
||||
expectToBeCalledOnce(task4);
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to cancel tasks', () => {
|
||||
const task1 = jest.fn();
|
||||
const task2 = createSequenceTask(1);
|
||||
const task3 = jest.fn();
|
||||
const task4 = createSequenceTask(2);
|
||||
taskQueue.enqueue(task1);
|
||||
taskQueue.enqueue(task2);
|
||||
taskQueue.enqueue(task3);
|
||||
taskQueue.enqueue(task4);
|
||||
taskQueue.cancelTasks([task1, task3]);
|
||||
clearTaskQueue(taskQueue);
|
||||
expect(task1).not.toBeCalled();
|
||||
expect(task3).not.toBeCalled();
|
||||
expectToBeCalledOnce(task2);
|
||||
expectToBeCalledOnce(task4);
|
||||
expect(taskQueue.hasTasksToProcess()).toBe(false);
|
||||
});
|
||||
|
||||
it('should not crash when last task is cancelled', () => {
|
||||
const task1 = jest.fn();
|
||||
taskQueue.enqueue(task1);
|
||||
taskQueue.cancelTasks([task1]);
|
||||
clearTaskQueue(taskQueue);
|
||||
expect(task1).not.toBeCalled();
|
||||
expect(taskQueue.hasTasksToProcess()).toBe(false);
|
||||
});
|
||||
|
||||
it('should not crash when task is cancelled between being started and resolved', () => {
|
||||
const task1 = jest.fn(() => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve();
|
||||
}, 1);
|
||||
});
|
||||
});
|
||||
|
||||
taskQueue.enqueue({ gen: task1, name: 'gen1' });
|
||||
taskQueue.processNext();
|
||||
taskQueue.cancelTasks([task1]);
|
||||
jest.runAllTimers();
|
||||
});
|
||||
});
|
||||
323
packages/react-native-web/src/exports/InteractionManager/__tests__/index-test.js
vendored
Normal file
323
packages/react-native-web/src/exports/InteractionManager/__tests__/index-test.js
vendored
Normal file
@@ -0,0 +1,323 @@
|
||||
/**
|
||||
* Copyright (c) Nicolas Gallagher.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
function expectToBeCalledOnce(fn) {
|
||||
expect(fn.mock.calls.length).toBe(1);
|
||||
}
|
||||
|
||||
describe('InteractionManager', () => {
|
||||
let InteractionManager;
|
||||
let interactionStart;
|
||||
let interactionComplete;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
InteractionManager = require('..');
|
||||
|
||||
interactionStart = jest.fn();
|
||||
interactionComplete = jest.fn();
|
||||
|
||||
InteractionManager.addListener(
|
||||
InteractionManager.Events.interactionStart,
|
||||
interactionStart
|
||||
);
|
||||
|
||||
InteractionManager.addListener(
|
||||
InteractionManager.Events.interactionComplete,
|
||||
interactionComplete
|
||||
);
|
||||
});
|
||||
|
||||
it('throws when clearing an undefined handle', () => {
|
||||
expect(() => InteractionManager.clearInteractionHandle()).toThrow();
|
||||
});
|
||||
|
||||
it('notifies asynchronously when interaction starts', () => {
|
||||
InteractionManager.createInteractionHandle();
|
||||
expect(interactionStart).not.toBeCalled();
|
||||
|
||||
jest.runAllTimers();
|
||||
expect(interactionStart).toBeCalled();
|
||||
expect(interactionComplete).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('notifies asynchronously when interaction stops', () => {
|
||||
const handle = InteractionManager.createInteractionHandle();
|
||||
jest.runAllTimers();
|
||||
interactionStart.mockClear();
|
||||
InteractionManager.clearInteractionHandle(handle);
|
||||
expect(interactionComplete).not.toBeCalled();
|
||||
|
||||
jest.runAllTimers();
|
||||
expect(interactionStart).not.toBeCalled();
|
||||
expect(interactionComplete).toBeCalled();
|
||||
});
|
||||
|
||||
it('does not notify when started & stoped in same event loop', () => {
|
||||
const handle = InteractionManager.createInteractionHandle();
|
||||
InteractionManager.clearInteractionHandle(handle);
|
||||
|
||||
jest.runAllTimers();
|
||||
expect(interactionStart).not.toBeCalled();
|
||||
expect(interactionComplete).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('does not notify when going from two -> one active interactions', () => {
|
||||
InteractionManager.createInteractionHandle();
|
||||
const handle = InteractionManager.createInteractionHandle();
|
||||
jest.runAllTimers();
|
||||
|
||||
interactionStart.mockClear();
|
||||
interactionComplete.mockClear();
|
||||
|
||||
InteractionManager.clearInteractionHandle(handle);
|
||||
jest.runAllTimers();
|
||||
expect(interactionStart).not.toBeCalled();
|
||||
expect(interactionComplete).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('run tasks asynchronously when there are interactions', () => {
|
||||
const task = jest.fn();
|
||||
InteractionManager.runAfterInteractions(task);
|
||||
expect(task).not.toBeCalled();
|
||||
|
||||
jest.runAllTimers();
|
||||
expect(task).toBeCalled();
|
||||
});
|
||||
|
||||
it('runs tasks when interactions complete', () => {
|
||||
const task = jest.fn();
|
||||
const handle = InteractionManager.createInteractionHandle();
|
||||
InteractionManager.runAfterInteractions(task);
|
||||
|
||||
jest.runAllTimers();
|
||||
InteractionManager.clearInteractionHandle(handle);
|
||||
expect(task).not.toBeCalled();
|
||||
|
||||
jest.runAllTimers();
|
||||
expect(task).toBeCalled();
|
||||
});
|
||||
|
||||
it('does not run tasks twice', () => {
|
||||
const task1 = jest.fn();
|
||||
const task2 = jest.fn();
|
||||
InteractionManager.runAfterInteractions(task1);
|
||||
jest.runAllTimers();
|
||||
|
||||
InteractionManager.runAfterInteractions(task2);
|
||||
jest.runAllTimers();
|
||||
|
||||
expectToBeCalledOnce(task1);
|
||||
});
|
||||
|
||||
it('runs tasks added while processing previous tasks', () => {
|
||||
const task1 = jest.fn(() => {
|
||||
InteractionManager.runAfterInteractions(task2);
|
||||
});
|
||||
const task2 = jest.fn();
|
||||
|
||||
InteractionManager.runAfterInteractions(task1);
|
||||
expect(task2).not.toBeCalled();
|
||||
|
||||
jest.runAllTimers();
|
||||
|
||||
expect(task1).toBeCalled();
|
||||
expect(task2).toBeCalled();
|
||||
});
|
||||
|
||||
it('allows tasks to be cancelled', () => {
|
||||
const task1 = jest.fn();
|
||||
const task2 = jest.fn();
|
||||
const promise1 = InteractionManager.runAfterInteractions(task1);
|
||||
InteractionManager.runAfterInteractions(task2);
|
||||
expect(task1).not.toBeCalled();
|
||||
expect(task2).not.toBeCalled();
|
||||
promise1.cancel();
|
||||
|
||||
jest.runAllTimers();
|
||||
expect(task1).not.toBeCalled();
|
||||
expect(task2).toBeCalled();
|
||||
});
|
||||
|
||||
it('should support promise variant', () => {
|
||||
expect.assertions(1);
|
||||
const task = jest.fn();
|
||||
const promise = InteractionManager.runAfterInteractions()
|
||||
.done(task)
|
||||
.then(() => {
|
||||
expect(task).toBeCalled();
|
||||
});
|
||||
jest.runAllTimers();
|
||||
return promise;
|
||||
});
|
||||
});
|
||||
|
||||
describe('promise tasks', () => {
|
||||
let InteractionManager;
|
||||
let sequenceId;
|
||||
|
||||
function createSequenceTask(expectedSequenceId) {
|
||||
return jest.fn(() => {
|
||||
expect(++sequenceId).toBe(expectedSequenceId);
|
||||
});
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
InteractionManager = require('..');
|
||||
sequenceId = 0;
|
||||
});
|
||||
|
||||
it('should run a basic promise task', () => {
|
||||
const task1 = jest.fn(() => {
|
||||
expect(++sequenceId).toBe(1);
|
||||
return new Promise((resolve) => resolve());
|
||||
});
|
||||
InteractionManager.runAfterInteractions({ gen: task1, name: 'gen1' });
|
||||
jest.runAllTimers();
|
||||
expectToBeCalledOnce(task1);
|
||||
});
|
||||
|
||||
it('should handle nested promises', () => {
|
||||
const task1 = jest.fn(() => {
|
||||
expect(++sequenceId).toBe(1);
|
||||
return new Promise((resolve) => {
|
||||
InteractionManager.runAfterInteractions({
|
||||
gen: task2,
|
||||
name: 'gen2'
|
||||
}).then(resolve);
|
||||
});
|
||||
});
|
||||
const task2 = jest.fn(() => {
|
||||
expect(++sequenceId).toBe(2);
|
||||
return new Promise((resolve) => resolve());
|
||||
});
|
||||
InteractionManager.runAfterInteractions({ gen: task1, name: 'gen1' });
|
||||
jest.runAllTimers();
|
||||
expectToBeCalledOnce(task1);
|
||||
expectToBeCalledOnce(task2);
|
||||
});
|
||||
|
||||
it('should pause promise tasks during interactions then resume', () => {
|
||||
const task1 = createSequenceTask(1);
|
||||
const task2 = jest.fn(() => {
|
||||
expect(++sequenceId).toBe(2);
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
InteractionManager.runAfterInteractions(task3).then(resolve);
|
||||
}, 1);
|
||||
});
|
||||
});
|
||||
const task3 = createSequenceTask(3);
|
||||
InteractionManager.runAfterInteractions(task1);
|
||||
InteractionManager.runAfterInteractions({ gen: task2, name: 'gen2' });
|
||||
jest.runOnlyPendingTimers();
|
||||
expectToBeCalledOnce(task1);
|
||||
expectToBeCalledOnce(task2);
|
||||
const handle = InteractionManager.createInteractionHandle();
|
||||
jest.runAllTimers();
|
||||
jest.runAllTimers(); // Just to be sure...
|
||||
expect(task3).not.toBeCalled();
|
||||
InteractionManager.clearInteractionHandle(handle);
|
||||
jest.runAllTimers();
|
||||
expectToBeCalledOnce(task3);
|
||||
});
|
||||
|
||||
it('should execute tasks in loop within deadline', () => {
|
||||
InteractionManager.setDeadline(100);
|
||||
const task1 = createSequenceTask(1);
|
||||
const task2 = createSequenceTask(2);
|
||||
InteractionManager.runAfterInteractions(task1);
|
||||
InteractionManager.runAfterInteractions(task2);
|
||||
|
||||
jest.runOnlyPendingTimers();
|
||||
expectToBeCalledOnce(task1);
|
||||
expectToBeCalledOnce(task2);
|
||||
});
|
||||
|
||||
it('should execute tasks one at a time if deadline exceeded', () => {
|
||||
InteractionManager.setDeadline(100);
|
||||
const task1 = jest.fn(() => {
|
||||
expect(++sequenceId).toBe(1);
|
||||
jest.setSystemTime(Date.now() + 200);
|
||||
});
|
||||
const task2 = createSequenceTask(2);
|
||||
InteractionManager.runAfterInteractions(task1);
|
||||
InteractionManager.runAfterInteractions(task2);
|
||||
|
||||
jest.runOnlyPendingTimers();
|
||||
|
||||
expectToBeCalledOnce(task1);
|
||||
expect(task2).not.toBeCalled();
|
||||
|
||||
jest.runOnlyPendingTimers();
|
||||
|
||||
expectToBeCalledOnce(task2);
|
||||
});
|
||||
|
||||
const bigAsyncTest = (resolveTest) => {
|
||||
const task1 = createSequenceTask(1);
|
||||
const task2 = jest.fn(() => {
|
||||
expect(++sequenceId).toBe(2);
|
||||
return new Promise((resolve) => {
|
||||
InteractionManager.runAfterInteractions(task3);
|
||||
setTimeout(() => {
|
||||
InteractionManager.runAfterInteractions({
|
||||
gen: task4,
|
||||
name: 'gen4'
|
||||
})
|
||||
.then(resolve)
|
||||
.then(() => {
|
||||
// Explicit exhaustion of the task queue is required
|
||||
jest.runAllTimers();
|
||||
});
|
||||
}, 1);
|
||||
});
|
||||
});
|
||||
const task3 = createSequenceTask(3);
|
||||
const task4 = jest.fn(() => {
|
||||
expect(++sequenceId).toBe(4);
|
||||
return new Promise((resolve) => {
|
||||
InteractionManager.runAfterInteractions(task5)
|
||||
.then(resolve)
|
||||
.then(() => {
|
||||
// Explicit exhaustion of the task queue is required
|
||||
jest.runAllTimers();
|
||||
});
|
||||
});
|
||||
});
|
||||
const task5 = createSequenceTask(5);
|
||||
const task6 = createSequenceTask(6);
|
||||
|
||||
InteractionManager.runAfterInteractions(task1);
|
||||
InteractionManager.runAfterInteractions({ gen: task2, name: 'gen2' });
|
||||
InteractionManager.runAfterInteractions(task6).then(() => {
|
||||
expectToBeCalledOnce(task1);
|
||||
expectToBeCalledOnce(task2);
|
||||
expectToBeCalledOnce(task3);
|
||||
expectToBeCalledOnce(task4);
|
||||
expectToBeCalledOnce(task5);
|
||||
expectToBeCalledOnce(task6);
|
||||
resolveTest();
|
||||
});
|
||||
|
||||
jest.runAllTimers();
|
||||
};
|
||||
|
||||
it('resolves async tasks recursively before other queued tasks', () => {
|
||||
return new Promise(bigAsyncTest);
|
||||
});
|
||||
|
||||
it('should also work with a deadline', () => {
|
||||
InteractionManager.setDeadline(100);
|
||||
const task = jest.fn(() => {
|
||||
jest.setSystemTime(Date.now() + 200);
|
||||
});
|
||||
InteractionManager.runAfterInteractions(task);
|
||||
return new Promise(bigAsyncTest);
|
||||
});
|
||||
});
|
||||
@@ -9,9 +9,16 @@
|
||||
*/
|
||||
|
||||
import invariant from 'fbjs/lib/invariant';
|
||||
import requestIdleCallback, {
|
||||
cancelIdleCallback
|
||||
} from '../../modules/requestIdleCallback';
|
||||
import type { Task } from './TaskQueue';
|
||||
import TaskQueue from './TaskQueue';
|
||||
import type { EventSubscription } from '../../vendor/react-native/vendor/emitter/EventEmitter';
|
||||
import EventEmitter from '../../vendor/react-native/vendor/emitter/EventEmitter';
|
||||
import requestIdleCallback from '../../modules/requestIdleCallback';
|
||||
|
||||
const _emitter = new EventEmitter<{
|
||||
interactionComplete: [],
|
||||
interactionStart: []
|
||||
}>();
|
||||
|
||||
const InteractionManager = {
|
||||
Events: {
|
||||
@@ -22,27 +29,28 @@ const InteractionManager = {
|
||||
/**
|
||||
* Schedule a function to run after all interactions have completed.
|
||||
*/
|
||||
runAfterInteractions(task: ?Function): {
|
||||
runAfterInteractions(task: ?Task): {
|
||||
then: Function,
|
||||
done: Function,
|
||||
cancel: Function
|
||||
} {
|
||||
let handle;
|
||||
|
||||
const tasks: Array<Task> = [];
|
||||
const promise = new Promise((resolve) => {
|
||||
handle = requestIdleCallback(() => {
|
||||
if (task) {
|
||||
resolve(task());
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
_scheduleUpdate();
|
||||
if (task) {
|
||||
tasks.push(task);
|
||||
}
|
||||
tasks.push({
|
||||
run: resolve,
|
||||
name: 'resolve ' + ((task && task.name) || '?')
|
||||
});
|
||||
_taskQueue.enqueueTasks(tasks);
|
||||
});
|
||||
return {
|
||||
then: promise.then.bind(promise),
|
||||
done: promise.then.bind(promise),
|
||||
cancel: () => {
|
||||
cancelIdleCallback(handle);
|
||||
_taskQueue.cancelTasks(tasks);
|
||||
}
|
||||
};
|
||||
},
|
||||
@@ -51,7 +59,10 @@ const InteractionManager = {
|
||||
* Notify manager that an interaction has started.
|
||||
*/
|
||||
createInteractionHandle(): number {
|
||||
return 1;
|
||||
_scheduleUpdate();
|
||||
const handle = ++_inc;
|
||||
_addInteractionSet.add(handle);
|
||||
return handle;
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -59,9 +70,73 @@ const InteractionManager = {
|
||||
*/
|
||||
clearInteractionHandle(handle: number) {
|
||||
invariant(!!handle, 'Must provide a handle to clear.');
|
||||
_scheduleUpdate();
|
||||
_addInteractionSet.delete(handle);
|
||||
_deleteInteractionSet.add(handle);
|
||||
},
|
||||
|
||||
addListener: () => {}
|
||||
addListener: (_emitter.addListener.bind(_emitter): EventSubscription),
|
||||
|
||||
/**
|
||||
*
|
||||
* @param deadline
|
||||
*/
|
||||
setDeadline(deadline: number) {
|
||||
_deadline = deadline;
|
||||
}
|
||||
};
|
||||
|
||||
const _interactionSet = new Set();
|
||||
const _addInteractionSet = new Set();
|
||||
const _deleteInteractionSet = new Set();
|
||||
const _taskQueue = new TaskQueue({ onMoreTasks: _scheduleUpdate });
|
||||
let _nextUpdateHandle: TimeoutID | number = 0;
|
||||
let _inc = 0;
|
||||
let _deadline = -1;
|
||||
|
||||
/**
|
||||
* Schedule an asynchronous update to the interaction state.
|
||||
*/
|
||||
function _scheduleUpdate() {
|
||||
if (!_nextUpdateHandle) {
|
||||
if (_deadline > 0) {
|
||||
_nextUpdateHandle = setTimeout(_processUpdate);
|
||||
} else {
|
||||
_nextUpdateHandle = requestIdleCallback(_processUpdate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify listeners, process queue, etc
|
||||
*/
|
||||
function _processUpdate() {
|
||||
_nextUpdateHandle = 0;
|
||||
const interactionCount = _interactionSet.size;
|
||||
_addInteractionSet.forEach((handle) => _interactionSet.add(handle));
|
||||
_deleteInteractionSet.forEach((handle) => _interactionSet.delete(handle));
|
||||
const nextInteractionCount = _interactionSet.size;
|
||||
|
||||
if (interactionCount !== 0 && nextInteractionCount === 0) {
|
||||
_emitter.emit(InteractionManager.Events.interactionComplete);
|
||||
} else if (interactionCount === 0 && nextInteractionCount !== 0) {
|
||||
_emitter.emit(InteractionManager.Events.interactionStart);
|
||||
}
|
||||
|
||||
if (nextInteractionCount === 0) {
|
||||
// It seems that we can't know the running time of the current event loop,
|
||||
// we can only calculate the running time of the current task queue.
|
||||
const begin = Date.now();
|
||||
while (_taskQueue.hasTasksToProcess()) {
|
||||
_taskQueue.processNext();
|
||||
if (_deadline > 0 && Date.now() - begin >= _deadline) {
|
||||
_scheduleUpdate();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
_addInteractionSet.clear();
|
||||
_deleteInteractionSet.clear();
|
||||
}
|
||||
|
||||
export default InteractionManager;
|
||||
|
||||
Reference in New Issue
Block a user