diff --git a/.changeset/silly-rocks-guess.md b/.changeset/silly-rocks-guess.md
deleted file mode 100644
index d1845455b0..0000000000
--- a/.changeset/silly-rocks-guess.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-"@lynx-js/react": minor
----
-
-Reverts #239: "batch multiple patches for main thread communication"
-
-This reverts the change that batched updates sent to the main thread in a single render pass.
diff --git a/packages/react/runtime/__test__/lifecycle.test.jsx b/packages/react/runtime/__test__/lifecycle.test.jsx
index bfb64ad1e5..95087ac645 100644
--- a/packages/react/runtime/__test__/lifecycle.test.jsx
+++ b/packages/react/runtime/__test__/lifecycle.test.jsx
@@ -1,18 +1,20 @@
-import { Component, options, render } from 'preact';
+import { Component, options } from 'preact';
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
import { useEffect, useLayoutEffect, useState } from '../src/index';
import { globalEnvManager } from './utils/envManager';
-import { waitSchedule } from './utils/nativeMethod';
import { initDelayUnmount } from '../src/lifecycle/delayUnmount';
import { globalCommitTaskMap, replaceCommitHook, replaceRequestAnimationFrame } from '../src/lifecycle/patch/commit';
import { deinitGlobalSnapshotPatch, initGlobalSnapshotPatch } from '../src/lifecycle/patch/snapshotPatch';
+import { renderBackground as render } from '../src/lifecycle/render';
import { LifecycleConstant } from '../src/lifecycleConstant';
import { CATCH_ERROR } from '../src/renderToOpcodes/constants';
import { __root } from '../src/root';
import { backgroundSnapshotInstanceManager, setupPage } from '../src/snapshot';
+import { elementTree, waitSchedule } from './utils/nativeMethod';
beforeAll(() => {
+ options.debounceRendering = Promise.prototype.then.bind(Promise.resolve());
setupPage(__CreatePage('0', 0));
replaceCommitHook();
initDelayUnmount();
@@ -28,6 +30,7 @@ afterEach(() => {
globalEnvManager.resetEnv();
deinitGlobalSnapshotPatch();
vi.restoreAllMocks();
+ elementTree.clear();
});
describe('useEffect', () => {
@@ -801,4 +804,118 @@ describe('useState', () => {
);
}
});
+
+ it('should batch multiple updates', async function() {
+ let _setCount;
+ let _setCount2;
+
+ const Child1 = () => {
+ const [count, setCount] = useState(0);
+ _setCount = setCount;
+ return (
+
+
+
+ );
+ };
+
+ const Child2 = () => {
+ const [count, setCount] = useState(0);
+ _setCount2 = setCount;
+ return (
+
+
+
+ );
+ };
+
+ const Comp = () => {
+ return (
+
+
+
+
+ );
+ };
+
+ // main thread render
+ {
+ __root.__jsx = ;
+ renderPage();
+ }
+
+ // background render
+ {
+ globalEnvManager.switchToBackground();
+ render(, __root);
+ }
+
+ // hydrate
+ {
+ // LifecycleConstant.firstScreen
+ lynxCoreInject.tt.OnLifecycleEvent(...globalThis.__OnLifecycleEvent.mock.calls[0]);
+
+ // rLynxChange
+ globalEnvManager.switchToMainThread();
+ globalThis.__OnLifecycleEvent.mockClear();
+ const rLynxChange = lynx.getNativeApp().callLepusMethod.mock.calls[0];
+ globalThis[rLynxChange[0]](rLynxChange[1]);
+ expect(globalThis.__OnLifecycleEvent).not.toBeCalled();
+ }
+
+ // insert node
+ {
+ globalEnvManager.switchToBackground();
+ lynx.getNativeApp().callLepusMethod.mockClear();
+ _setCount(1);
+ _setCount2(2);
+ await waitSchedule();
+
+ expect(lynx.getNativeApp().callLepusMethod).toHaveBeenCalledTimes(1);
+ expect(lynx.getNativeApp().callLepusMethod.mock.calls).toMatchInlineSnapshot(
+ `
+ [
+ [
+ "rLynxChange",
+ {
+ "data": "{"patchList":[{"id":42,"snapshotPatch":[3,-3,0,1]},{"id":43,"snapshotPatch":[3,-4,0,2]}]}",
+ "patchOptions": {
+ "reloadVersion": 0,
+ },
+ },
+ [Function],
+ ],
+ ]
+ `,
+ );
+ }
+
+ {
+ globalEnvManager.switchToMainThread();
+ globalThis.__OnLifecycleEvent.mockClear();
+ const rLynxChange = lynx.getNativeApp().callLepusMethod.mock.calls[0];
+ globalThis[rLynxChange[0]](rLynxChange[1]);
+ rLynxChange[2];
+ lynx.getNativeApp().callLepusMethod.mockClear();
+ await waitSchedule();
+ expect(__root.__element_root).toMatchInlineSnapshot(`
+
+
+
+
+
+
+
+
+
+
+ `);
+ }
+ });
});
diff --git a/packages/react/runtime/__test__/lynx/timing.test.jsx b/packages/react/runtime/__test__/lynx/timing.test.jsx
index d4a575c0b7..04601e217a 100644
--- a/packages/react/runtime/__test__/lynx/timing.test.jsx
+++ b/packages/react/runtime/__test__/lynx/timing.test.jsx
@@ -3,11 +3,12 @@
// Licensed under the Apache License Version 2.0 that can be found in the
// LICENSE file in the root directory of this source tree.
*/
-import { Component, options, render } from 'preact';
+import { Component, options } from 'preact';
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
import { replaceCommitHook } from '../../src/lifecycle/patch/commit';
import { injectUpdateMainThread } from '../../src/lifecycle/patch/updateMainThread';
+import { renderBackground as render } from '../../src/lifecycle/render';
import '../../src/lynx/component';
import { initTimingAPI } from '../../src/lynx/performance';
import { __root } from '../../src/root';
diff --git a/packages/react/runtime/__test__/snapshot/gesture.test.jsx b/packages/react/runtime/__test__/snapshot/gesture.test.jsx
index 9de53ab92e..a90dee9e38 100644
--- a/packages/react/runtime/__test__/snapshot/gesture.test.jsx
+++ b/packages/react/runtime/__test__/snapshot/gesture.test.jsx
@@ -1,8 +1,8 @@
-import { render } from 'preact';
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
import { replaceCommitHook } from '../../src/lifecycle/patch/commit';
import { injectUpdateMainThread } from '../../src/lifecycle/patch/updateMainThread';
+import { renderBackground as render } from '../../src/lifecycle/render';
import { __root } from '../../src/root';
import { setupPage } from '../../src/snapshot';
import { globalEnvManager } from '../utils/envManager';
diff --git a/packages/react/runtime/__test__/snapshot/ref.test.jsx b/packages/react/runtime/__test__/snapshot/ref.test.jsx
index 66e1251f8c..c0421f3848 100644
--- a/packages/react/runtime/__test__/snapshot/ref.test.jsx
+++ b/packages/react/runtime/__test__/snapshot/ref.test.jsx
@@ -3,13 +3,13 @@
// Licensed under the Apache License Version 2.0 that can be found in the
// LICENSE file in the root directory of this source tree.
*/
-import { render } from 'preact';
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
import { Component, createRef, root, useState } from '../../src/index';
import { delayedLifecycleEvents } from '../../src/lifecycle/event/delayLifecycleEvents';
import { clearCommitTaskId, replaceCommitHook } from '../../src/lifecycle/patch/commit';
import { injectUpdateMainThread } from '../../src/lifecycle/patch/updateMainThread';
+import { renderBackground as render } from '../../src/lifecycle/render';
import { __pendingListUpdates } from '../../src/list';
import { __root } from '../../src/root';
import { setupPage } from '../../src/snapshot';
diff --git a/packages/react/runtime/__test__/snapshot/workletEvent.test.jsx b/packages/react/runtime/__test__/snapshot/workletEvent.test.jsx
index 54712065ff..fb2202bbab 100644
--- a/packages/react/runtime/__test__/snapshot/workletEvent.test.jsx
+++ b/packages/react/runtime/__test__/snapshot/workletEvent.test.jsx
@@ -3,12 +3,12 @@
// Licensed under the Apache License Version 2.0 that can be found in the
// LICENSE file in the root directory of this source tree.
*/
-import { render } from 'preact';
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
import { Component, useState } from '../../src/index';
import { replaceCommitHook } from '../../src/lifecycle/patch/commit';
import { injectUpdateMainThread } from '../../src/lifecycle/patch/updateMainThread';
+import { renderBackground as render } from '../../src/lifecycle/render';
import { __root } from '../../src/root';
import { setupPage } from '../../src/snapshot';
import { globalEnvManager } from '../utils/envManager';
diff --git a/packages/react/runtime/__test__/snapshot/workletRef.test.jsx b/packages/react/runtime/__test__/snapshot/workletRef.test.jsx
index a4e78f0f2a..b7655c49a6 100644
--- a/packages/react/runtime/__test__/snapshot/workletRef.test.jsx
+++ b/packages/react/runtime/__test__/snapshot/workletRef.test.jsx
@@ -3,13 +3,13 @@
// Licensed under the Apache License Version 2.0 that can be found in the
// LICENSE file in the root directory of this source tree.
*/
-import { render } from 'preact';
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
import { createCompBG1, createCompBGList, createCompBGSpread } from './workletRefBG';
import { createCompMT1, createCompMTList, createCompMTSpread } from './workletRefMT';
import { replaceCommitHook } from '../../src/lifecycle/patch/commit';
import { injectUpdateMainThread } from '../../src/lifecycle/patch/updateMainThread';
+import { renderBackground as render } from '../../src/lifecycle/render';
import { __root } from '../../src/root';
import { setupPage } from '../../src/snapshot';
import { globalEnvManager } from '../utils/envManager';
diff --git a/packages/react/runtime/__test__/worklet/workletRef.test.jsx b/packages/react/runtime/__test__/worklet/workletRef.test.jsx
index 97b6fcc7d7..9eb2079f3e 100644
--- a/packages/react/runtime/__test__/worklet/workletRef.test.jsx
+++ b/packages/react/runtime/__test__/worklet/workletRef.test.jsx
@@ -3,11 +3,11 @@
// Licensed under the Apache License Version 2.0 that can be found in the
// LICENSE file in the root directory of this source tree.
*/
-import { render } from 'preact';
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
import { replaceCommitHook } from '../../src/lifecycle/patch/commit';
import { injectUpdateMainThread } from '../../src/lifecycle/patch/updateMainThread';
+import { renderBackground as render } from '../../src/lifecycle/render';
import { __root } from '../../src/root';
import { setupPage } from '../../src/snapshot';
import { destroyWorklet } from '../../src/worklet/destroy';
diff --git a/packages/react/runtime/src/lifecycle/destroy.ts b/packages/react/runtime/src/lifecycle/destroy.ts
index c37368685e..bdbf4fa6c9 100644
--- a/packages/react/runtime/src/lifecycle/destroy.ts
+++ b/packages/react/runtime/src/lifecycle/destroy.ts
@@ -1,19 +1,18 @@
// Copyright 2024 The Lynx Authors. All rights reserved.
// Licensed under the Apache License Version 2.0 that can be found in the
// LICENSE file in the root directory of this source tree.
-import { render } from 'preact';
-
import { __root } from '../root.js';
import { delayedEvents } from './event/delayEvents.js';
import { delayedLifecycleEvents } from './event/delayLifecycleEvents.js';
import { globalCommitTaskMap } from './patch/commit.js';
+import { renderBackground } from './render.js';
function destroyBackground(): void {
if (__PROFILE__) {
console.profile('destroyBackground');
}
- render(null, __root as any);
+ renderBackground(null, __root as any);
globalCommitTaskMap.forEach(task => {
task();
diff --git a/packages/react/runtime/src/lifecycle/patch/commit.ts b/packages/react/runtime/src/lifecycle/patch/commit.ts
index 2a50f4a768..296bd00ae0 100644
--- a/packages/react/runtime/src/lifecycle/patch/commit.ts
+++ b/packages/react/runtime/src/lifecycle/patch/commit.ts
@@ -30,6 +30,11 @@ let nextCommitTaskId = 1;
let globalBackgroundSnapshotInstancesToRemove: number[] = [];
+let patchesToCommit: Patch[] = [];
+function clearPatchesToCommit(): void {
+ patchesToCommit = [];
+}
+
interface Patch {
id: number;
snapshotPatch?: SnapshotPatch;
@@ -48,6 +53,28 @@ interface PatchOptions {
}
function replaceCommitHook(): void {
+ // use our own `options.debounceRendering` to insert a timing flag before render
+ type DebounceRendering = (f: () => void) => void;
+ const injectDebounceRendering = (debounceRendering: DebounceRendering): DebounceRendering => {
+ return (f: () => void) => {
+ debounceRendering(() => {
+ f();
+ void commitToMainThread();
+ });
+ };
+ };
+ const defaultDebounceRendering = options.debounceRendering?.bind(options)
+ ?? (Promise.prototype.then.bind(Promise.resolve()) as DebounceRendering);
+ let _debounceRendering = injectDebounceRendering(defaultDebounceRendering);
+ Object.defineProperty(options, 'debounceRendering', {
+ get() {
+ return _debounceRendering;
+ },
+ set(debounceRendering: DebounceRendering) {
+ _debounceRendering = injectDebounceRendering(debounceRendering);
+ },
+ });
+
const oldCommit = options[COMMIT];
const commit = async (vnode: VNode, commitQueue: any[]) => {
if (__LEPUS__) {
@@ -55,9 +82,6 @@ function replaceCommitHook(): void {
commitQueue.length = 0;
return;
}
-
- markTimingLegacy(PerformanceTimingKeys.updateDiffVdomEnd);
- markTiming(PerformanceTimingKeys.diffVdomEnd);
const renderCallbacks = commitQueue.map((component: Component) => {
const ret = {
component,
@@ -97,9 +121,7 @@ function replaceCommitHook(): void {
});
const snapshotPatch = takeGlobalSnapshotPatch();
- const flushOptions = globalFlushOptions;
const workletRefInitValuePatch = takeWorkletRefInitValuePatch();
- globalFlushOptions = {};
if (!snapshotPatch && workletRefInitValuePatch.length === 0) {
// before hydration, skip patch
return;
@@ -115,23 +137,44 @@ function replaceCommitHook(): void {
if (workletRefInitValuePatch.length) {
patch.workletRefInitValuePatch = workletRefInitValuePatch;
}
- const patchList: PatchList = {
- patchList: [patch],
- };
- if (!isEmptyObject(flushOptions)) {
- patchList.flushOptions = flushOptions;
- }
- const obj = commitPatchUpdate(patchList, {});
- lynx.getNativeApp().callLepusMethod(LifecycleConstant.patchUpdate, obj, () => {
- const commitTask = globalCommitTaskMap.get(commitTaskId);
+ patchesToCommit.push(patch);
+ };
+ options[COMMIT] = commit as ((...args: Parameters) => void);
+}
+
+async function commitToMainThread(): Promise {
+ if (patchesToCommit.length === 0) {
+ return;
+ }
+
+ markTimingLegacy(PerformanceTimingKeys.updateDiffVdomEnd);
+ markTiming(PerformanceTimingKeys.diffVdomEnd);
+
+ const flushOptions = globalFlushOptions;
+ globalFlushOptions = {};
+
+ const patchList: PatchList = {
+ patchList: patchesToCommit,
+ };
+ patchesToCommit = [];
+
+ if (!isEmptyObject(flushOptions)) {
+ patchList.flushOptions = flushOptions;
+ }
+
+ const obj = commitPatchUpdate(patchList, {});
+
+ lynx.getNativeApp().callLepusMethod(LifecycleConstant.patchUpdate, obj, () => {
+ for (let i = 0; i < patchList.patchList.length; i++) {
+ const patch = patchList.patchList[i]!;
+ const commitTask = globalCommitTaskMap.get(patch.id);
if (commitTask) {
commitTask();
- globalCommitTaskMap.delete(commitTaskId);
+ globalCommitTaskMap.delete(patch.id);
}
- });
- };
- options[COMMIT] = commit as ((...args: Parameters) => void);
+ }
+ });
}
function commitPatchUpdate(patchList: PatchList, patchOptions: Omit): {
@@ -189,12 +232,15 @@ function replaceRequestAnimationFrame(): void {
*/
export {
commitPatchUpdate,
+ commitToMainThread,
genCommitTaskId,
clearCommitTaskId,
globalBackgroundSnapshotInstancesToRemove,
globalCommitTaskMap,
globalFlushOptions,
nextCommitTaskId,
+ patchesToCommit,
+ clearPatchesToCommit,
replaceCommitHook,
replaceRequestAnimationFrame,
type PatchOptions,
diff --git a/packages/react/runtime/src/lifecycle/reload.ts b/packages/react/runtime/src/lifecycle/reload.ts
index b8d1bf9f03..9f7afb7ee2 100644
--- a/packages/react/runtime/src/lifecycle/reload.ts
+++ b/packages/react/runtime/src/lifecycle/reload.ts
@@ -1,8 +1,6 @@
// Copyright 2024 The Lynx Authors. All rights reserved.
// Licensed under the Apache License Version 2.0 that can be found in the
// LICENSE file in the root directory of this source tree.
-import { render } from 'preact';
-
import { hydrate } from '../hydrate.js';
import { LifecycleConstant } from '../lifecycleConstant.js';
import { __pendingListUpdates } from '../list.js';
@@ -15,7 +13,7 @@ import { destroyWorklet } from '../worklet/destroy.js';
import { clearJSReadyEventIdSwap, isJSReady } from './event/jsReady.js';
import { increaseReloadVersion } from './pass.js';
import { deinitGlobalSnapshotPatch } from './patch/snapshotPatch.js';
-import { renderMainThread } from './render.js';
+import { renderBackground, renderMainThread } from './render.js';
function reloadMainThread(data: any, options: UpdatePageOption): void {
if (__PROFILE__) {
@@ -76,7 +74,7 @@ function reloadBackground(updateData: Record): void {
// COW when modify `lynx.__initData` to make sure Provider & Consumer works
lynx.__initData = Object.assign({}, lynx.__initData, updateData);
- render(__root.__jsx, __root as any);
+ renderBackground(__root.__jsx, __root as any);
if (__PROFILE__) {
console.profileEnd();
diff --git a/packages/react/runtime/src/lifecycle/render.ts b/packages/react/runtime/src/lifecycle/render.ts
index c7823a72fa..1c67d0e547 100644
--- a/packages/react/runtime/src/lifecycle/render.ts
+++ b/packages/react/runtime/src/lifecycle/render.ts
@@ -2,10 +2,12 @@
// Licensed under the Apache License Version 2.0 that can be found in the
// LICENSE file in the root directory of this source tree.
import { render } from 'preact';
+import type { ComponentChild, ContainerNode } from 'preact';
import { renderOpcodesInto } from '../opcodes.js';
import { render as renderToString } from '../renderToOpcodes/index.js';
import { __root } from '../root.js';
+import { commitToMainThread } from './patch/commit.js';
function renderMainThread(): void {
/* v8 ignore start */
@@ -45,4 +47,9 @@ function renderMainThread(): void {
/* v8 ignore stop */
}
-export { renderMainThread };
+function renderBackground(vnode: ComponentChild, parent: ContainerNode): void {
+ render(vnode, parent);
+ void commitToMainThread();
+}
+
+export { renderMainThread, renderBackground };
diff --git a/packages/react/runtime/src/lynx-api.ts b/packages/react/runtime/src/lynx-api.ts
index 3c369b795a..25b274c0bd 100644
--- a/packages/react/runtime/src/lynx-api.ts
+++ b/packages/react/runtime/src/lynx-api.ts
@@ -1,13 +1,13 @@
// Copyright 2024 The Lynx Authors. All rights reserved.
// Licensed under the Apache License Version 2.0 that can be found in the
// LICENSE file in the root directory of this source tree.
-import { render } from 'preact';
import { createContext, createElement } from 'preact/compat';
import { useState } from 'preact/hooks';
import type { Consumer, FC, ReactNode } from 'react';
import { factory, withInitDataInState } from './compat/initData.js';
import { useLynxGlobalEventListener } from './hooks/useLynxGlobalEventListener.js';
+import { renderBackground } from './lifecycle/render.js';
import { LifecycleConstant } from './lifecycleConstant.js';
import { flushDelayedLifecycleEvents } from './lynx/tt.js';
import { __root } from './root.js';
@@ -86,7 +86,7 @@ export const root: Root = {
__root.__jsx = jsx;
} else {
__root.__jsx = jsx;
- render(jsx, __root as any);
+ renderBackground(jsx, __root as any);
if (__FIRST_SCREEN_SYNC_TIMING__ === 'immediately') {
// This is for cases where `root.render()` is called asynchronously,
// `firstScreen` message might have been reached.
diff --git a/packages/react/runtime/src/lynx/tt.ts b/packages/react/runtime/src/lynx/tt.ts
index 22679d9302..85306bb958 100644
--- a/packages/react/runtime/src/lynx/tt.ts
+++ b/packages/react/runtime/src/lynx/tt.ts
@@ -1,30 +1,34 @@
// Copyright 2024 The Lynx Authors. All rights reserved.
// Licensed under the Apache License Version 2.0 that can be found in the
// LICENSE file in the root directory of this source tree.
-import { render } from 'preact';
-
import { LifecycleConstant, NativeUpdateDataType } from '../lifecycleConstant.js';
import {
- PerformanceTimingFlags,
PerformanceTimingKeys,
+ PerformanceTimingFlags,
PipelineOrigins,
beginPipeline,
markTiming,
} from './performance.js';
import { BackgroundSnapshotInstance, hydrate } from '../backgroundSnapshot.js';
-import { runWithForce } from './runWithForce.js';
import { destroyBackground } from '../lifecycle/destroy.js';
import { delayedEvents, delayedPublishEvent } from '../lifecycle/event/delayEvents.js';
import { delayLifecycleEvent, delayedLifecycleEvents } from '../lifecycle/event/delayLifecycleEvents.js';
-import { commitPatchUpdate, genCommitTaskId, globalCommitTaskMap } from '../lifecycle/patch/commit.js';
-import type { PatchList } from '../lifecycle/patch/commit.js';
+import {
+ clearPatchesToCommit,
+ commitPatchUpdate,
+ genCommitTaskId,
+ globalCommitTaskMap,
+ patchesToCommit,
+ type PatchList,
+} from '../lifecycle/patch/commit.js';
import { reloadBackground } from '../lifecycle/reload.js';
+import { renderBackground } from '../lifecycle/render.js';
import { CHILDREN } from '../renderToOpcodes/constants.js';
import { __root } from '../root.js';
import { globalRefsToSet, updateBackgroundRefs } from '../snapshot/ref.js';
import { backgroundSnapshotInstanceManager } from '../snapshot.js';
import { destroyWorklet } from '../worklet/destroy.js';
-
+import { runWithForce } from './runWithForce.js';
export { runWithForce };
function injectTt(): void {
@@ -125,11 +129,14 @@ function onLifecycleEventImpl(type: string, data: any): void {
console.profile('commitChanges');
}
const commitTaskId = genCommitTaskId();
+ patchesToCommit.push(
+ { snapshotPatch, id: commitTaskId },
+ );
const patchList: PatchList = {
- patchList: [{ snapshotPatch, id: commitTaskId }],
+ patchList: patchesToCommit,
};
+ clearPatchesToCommit();
const obj = commitPatchUpdate(patchList, { isHydration: true });
-
lynx.getNativeApp().callLepusMethod(LifecycleConstant.patchUpdate, obj, () => {
updateBackgroundRefs(commitTaskId);
globalCommitTaskMap.forEach((commitTask, id) => {
@@ -205,7 +212,7 @@ function updateGlobalProps(newData: Record): void {
// This is already done because updateFromRoot will consume all dirty flags marked by
// the setState, and setState's flush will be a noop. No extra diffs will be needed.
Promise.resolve().then(() => {
- runWithForce(() => render(__root.__jsx, __root as any));
+ runWithForce(() => renderBackground(__root.__jsx, __root as any));
});
lynxCoreInject.tt.GlobalEventEmitter.emit('onGlobalPropsChanged');
}
diff --git a/packages/react/testing-library/src/__tests__/worklet.test.jsx b/packages/react/testing-library/src/__tests__/worklet.test.jsx
index b17ffeda7f..221a73a413 100644
--- a/packages/react/testing-library/src/__tests__/worklet.test.jsx
+++ b/packages/react/testing-library/src/__tests__/worklet.test.jsx
@@ -441,24 +441,7 @@ describe('worklet', () => {
[
"rLynxChange",
{
- "data": "{"patchList":[{"id":1,"workletRefInitValuePatch":[[1,null],[2,0]]}]}",
- "patchOptions": {
- "pipelineOptions": {
- "dsl": "reactLynx",
- "needTimestamps": true,
- "pipelineID": "pipelineID",
- "pipelineOrigin": "reactLynxHydrate",
- "stage": "hydrate",
- },
- "reloadVersion": 0,
- },
- },
- [Function],
- ],
- [
- "rLynxChange",
- {
- "data": "{"patchList":[{"snapshotPatch":[3,-2,0,{"_wvid":1},3,-2,1,{"_c":{"ref":{"_wvid":1},"num":{"_wvid":2}},"_wkltId":"a45f:test:9","_execId":1}],"id":2}]}",
+ "data": "{"patchList":[{"id":1,"workletRefInitValuePatch":[[1,null],[2,0]]},{"snapshotPatch":[3,-2,0,{"_wvid":1},3,-2,1,{"_c":{"ref":{"_wvid":1},"num":{"_wvid":2}},"_wkltId":"a45f:test:9","_execId":1}],"id":2}]}",
"patchOptions": {
"isHydration": true,
"pipelineOptions": {
diff --git a/packages/react/testing-library/src/pure.jsx b/packages/react/testing-library/src/pure.jsx
index 92064e9346..cad285ac72 100644
--- a/packages/react/testing-library/src/pure.jsx
+++ b/packages/react/testing-library/src/pure.jsx
@@ -8,9 +8,9 @@ import { act } from 'preact/test-utils';
import { __root } from '@lynx-js/react/internal';
-import { commitToMainThread } from '../../runtime/lib/lifecycle/patch/commit.js';
import { flushDelayedLifecycleEvents } from '../../runtime/lib/lynx/tt.js';
import { clearPage } from '../../runtime/lib/snapshot.js';
+import { commitToMainThread } from '../../runtime/lib/lifecycle/patch/commit.js';
export function waitSchedule() {
return new Promise(resolve => {
@@ -93,6 +93,8 @@ export function cleanup() {
globalThis.lynxEnv.switchToBackgroundThread();
act(() => {
preactRender(null, __root);
+ // This is needed to ensure that the ui updates are sent to the main thread
+ commitToMainThread();
});
lynxEnv.mainThread.elementTree.root = undefined;
diff --git a/packages/react/testing-library/src/vitest-global-setup.js b/packages/react/testing-library/src/vitest-global-setup.js
index 3cf3f44542..d8dc92bb52 100644
--- a/packages/react/testing-library/src/vitest-global-setup.js
+++ b/packages/react/testing-library/src/vitest-global-setup.js
@@ -1,21 +1,23 @@
import { options } from 'preact';
-
+import { SnapshotInstance } from '../../runtime/lib/snapshot.js';
+import { snapshotInstanceManager } from '../../runtime/lib/snapshot.js';
import { BackgroundSnapshotInstance } from '../../runtime/lib/backgroundSnapshot.js';
-import { clearCommitTaskId, replaceCommitHook } from '../../runtime/lib/lifecycle/patch/commit.js';
-import { deinitGlobalSnapshotPatch } from '../../runtime/lib/lifecycle/patch/snapshotPatch.js';
-import { injectUpdateMainThread } from '../../runtime/lib/lifecycle/patch/updateMainThread.js';
+import { backgroundSnapshotInstanceManager } from '../../runtime/lib/snapshot.js';
import { injectCalledByNative } from '../../runtime/lib/lynx/calledByNative.js';
-import { flushDelayedLifecycleEvents, injectTt } from '../../runtime/lib/lynx/tt.js';
-import { setRoot } from '../../runtime/lib/root.js';
+import { injectUpdateMainThread } from '../../runtime/lib/lifecycle/patch/updateMainThread.js';
import {
- SnapshotInstance,
- backgroundSnapshotInstanceManager,
- snapshotInstanceManager,
-} from '../../runtime/lib/snapshot.js';
-import { destroyWorklet } from '../../runtime/lib/worklet/destroy.js';
+ replaceCommitHook,
+ clearPatchesToCommit,
+ clearCommitTaskId,
+} from '../../runtime/lib/lifecycle/patch/commit.js';
+import { injectTt } from '../../runtime/lib/lynx/tt.js';
+import { setRoot } from '../../runtime/lib/root.js';
+import { deinitGlobalSnapshotPatch } from '../../runtime/lib/lifecycle/patch/snapshotPatch.js';
import { initApiEnv } from '../../worklet-runtime/lib/api/lynxApi.js';
import { initEventListeners } from '../../worklet-runtime/lib/listeners.js';
import { initWorklet } from '../../worklet-runtime/lib/workletRuntime.js';
+import { destroyWorklet } from '../../runtime/lib/worklet/destroy.js';
+import { flushDelayedLifecycleEvents } from '../../runtime/lib/lynx/tt.js';
const {
onInjectMainThreadGlobals,
@@ -131,6 +133,7 @@ globalThis.onInjectBackgroundThreadGlobals = (target) => {
// re-init global snapshot patch to undefined
deinitGlobalSnapshotPatch();
+ clearPatchesToCommit();
clearCommitTaskId();
};
globalThis.onResetLynxEnv = () => {